From 831c97956fbc9488c273e479045a83fdd384364c Mon Sep 17 00:00:00 2001 From: Enzo Bom Date: Wed, 17 Dec 2025 17:52:14 -0300 Subject: [PATCH 1/6] Add security configs and a feign client for the User MS --- build.gradle | 7 +++ .../taskscheduler/business/dto/UserDTO.java | 15 +++++ .../infrastructure/client/UserClient.java | 14 +++++ .../security/JwtRequestFilter.java | 60 +++++++++++++++++++ .../infrastructure/security/JwtUtil.java | 45 ++++++++++++++ .../security/SecurityConfig.java | 53 ++++++++++++++++ .../security/UserDetailsServiceImpl.java | 23 +++++++ src/main/resources/application.properties | 1 + 8 files changed, 218 insertions(+) create mode 100644 src/main/java/com/javanauta/taskscheduler/business/dto/UserDTO.java create mode 100644 src/main/java/com/javanauta/taskscheduler/infrastructure/client/UserClient.java create mode 100644 src/main/java/com/javanauta/taskscheduler/infrastructure/security/JwtRequestFilter.java create mode 100644 src/main/java/com/javanauta/taskscheduler/infrastructure/security/JwtUtil.java create mode 100644 src/main/java/com/javanauta/taskscheduler/infrastructure/security/SecurityConfig.java create mode 100644 src/main/java/com/javanauta/taskscheduler/infrastructure/security/UserDetailsServiceImpl.java diff --git a/build.gradle b/build.gradle index 8589ac2..1177756 100644 --- a/build.gradle +++ b/build.gradle @@ -33,12 +33,19 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-webmvc' implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + implementation 'io.jsonwebtoken:jjwt-api:0.13.0' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-data-mongodb-test' testImplementation 'org.springframework.boot:spring-boot-starter-security-test' testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.13.0' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.13.0' } dependencyManagement { diff --git a/src/main/java/com/javanauta/taskscheduler/business/dto/UserDTO.java b/src/main/java/com/javanauta/taskscheduler/business/dto/UserDTO.java new file mode 100644 index 0000000..11b71fe --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/business/dto/UserDTO.java @@ -0,0 +1,15 @@ +package com.javanauta.taskscheduler.business.dto; + +import lombok.*; + +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class UserDTO { + private String email; + private String password; +} diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/client/UserClient.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/client/UserClient.java new file mode 100644 index 0000000..26fa406 --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/client/UserClient.java @@ -0,0 +1,14 @@ +package com.javanauta.taskscheduler.infrastructure.client; + +import com.javanauta.taskscheduler.business.dto.UserDTO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "user", url = "${user.url}") +public interface UserClient { + + @GetMapping + UserDTO getUserByEmail(@RequestParam("email") String email, @RequestHeader("Authorization") String token); +} diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/security/JwtRequestFilter.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/security/JwtRequestFilter.java new file mode 100644 index 0000000..13f665f --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/security/JwtRequestFilter.java @@ -0,0 +1,60 @@ +package com.javanauta.taskscheduler.infrastructure.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +// Defines the JwtRequestFilter class, which extends OncePerRequestFilter +public class JwtRequestFilter extends OncePerRequestFilter { + + // Defines properties to store instances of JwtUtil and UserDetailsService + private final JwtUtil jwtUtil; + private final UserDetailsServiceImpl userDetailsService; + + // Constructor that initializes the properties with provided instances + public JwtRequestFilter(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService) { + this.jwtUtil = jwtUtil; + this.userDetailsService = userDetailsService; + } + + // Method called once per request to process the filter + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + + // Gets the value of the "Authorization" header from the request + final String authorizationHeader = request.getHeader("Authorization"); + + // Checks whether the header exists and starts with "Bearer " + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + // Extracts the JWT token from the header + final String token = authorizationHeader.substring(7); + // Extracts the username from the JWT token + final String username = jwtUtil.extractUsername(token); + + // If the username is not null and the user is not yet authenticated + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + // Loads user details from the username + UserDetails userDetails = userDetailsService.loadUserDetails(username, token); + // Validates the JWT token + if (jwtUtil.validateToken(token, username)) { + // Creates an authentication object with user information + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + // Sets authentication in the security context + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + } + + // Continues the filter chain, allowing the request to proceed + chain.doFilter(request, response); + } +} diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/security/JwtUtil.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/security/JwtUtil.java new file mode 100644 index 0000000..530ba22 --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/security/JwtUtil.java @@ -0,0 +1,45 @@ +package com.javanauta.taskscheduler.infrastructure.security; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@Service +public class JwtUtil { + + // Secret key used to sign and verify JWT tokens + private final String secretKey = "your-super-long-secret-key-that-needs-to-be-very-difficult"; + + // Extracts claims from the JWT token + public Claims extractClaims(String token) { + return Jwts.parser() + .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8))) // Sets secret key to validate signature + .build() + .parseClaimsJws(token) // Parses the JWT token and gets claims + .getBody(); // Returns claims body + } + + // Extracts username from the JWT token + public String extractUsername(String token) { + // Gets the subject (username) from the claims + return extractClaims(token).getSubject(); + } + + // Checks whether the JWT token is expired + public boolean isTokenExpired(String token) { + // Compares token expiration date with current date + return extractClaims(token).getExpiration().before(new Date()); + } + + // Validates the JWT token by checking username match and expiration + public boolean validateToken(String token, String username) { + // Extracts username from the token + final String extractedUsername = extractUsername(token); + // Checks if username matches and token is not expired + return (extractedUsername.equals(username) && !isTokenExpired(token)); + } +} diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/security/SecurityConfig.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/security/SecurityConfig.java new file mode 100644 index 0000000..f85ca7d --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/security/SecurityConfig.java @@ -0,0 +1,53 @@ +package com.javanauta.taskscheduler.infrastructure.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + // JwtUtil and UserDetailsService instances injected by Spring + private final JwtUtil jwtUtil; + private final UserDetailsServiceImpl userDetailsService; + + // Constructor for dependency injection of JwtUtil and UserDetailsService + @Autowired + public SecurityConfig(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService) { + this.jwtUtil = jwtUtil; + this.userDetailsService = userDetailsService; + } + + // Security filter configuration + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // Creates a JwtRequestFilter instance using JwtUtil and UserDetailsService + JwtRequestFilter jwtRequestFilter = new JwtRequestFilter(jwtUtil, userDetailsService); + + http + .csrf(AbstractHttpConfigurer::disable) // Disables CSRF protection for REST APIs (not needed in stateless APIs) + .authorizeHttpRequests(authorize -> authorize + .anyRequest().authenticated() // Requires authentication for all other requests + ) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Sets session policy to stateless + ) + .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); // Adds JWT filter before default authentication filter + + // Returns the built security configuration + return http.build(); + } +} diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/security/UserDetailsServiceImpl.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/security/UserDetailsServiceImpl.java new file mode 100644 index 0000000..dc1c16c --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/security/UserDetailsServiceImpl.java @@ -0,0 +1,23 @@ +package com.javanauta.taskscheduler.infrastructure.security; + +import com.javanauta.taskscheduler.business.dto.UserDTO; +import com.javanauta.taskscheduler.infrastructure.client.UserClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; + +@Service +public class UserDetailsServiceImpl { + + @Autowired + private UserClient userClient; + + public UserDetails loadUserDetails(String email, String token) { + UserDTO userDTO = userClient.getUserByEmail(email, token); + + return org.springframework.security.core.userdetails.User + .withUsername(userDTO.getEmail()) // Sets the username as the email + .password(userDTO.getPassword()) // Sets the user's password + .build(); // Builds the UserDetails object + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index bbc2dd4..2ad97a1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,2 @@ spring.application.name=task-scheduler +user.url=localhost:8080 From 5986931c22dda7d57110306405939bae578a570b Mon Sep 17 00:00:00 2001 From: Enzo Bom Date: Wed, 17 Dec 2025 18:02:26 -0300 Subject: [PATCH 2/6] Fix based on PR review --- .../taskscheduler/infrastructure/client/UserClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/client/UserClient.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/client/UserClient.java index 26fa406..7fa06d0 100644 --- a/src/main/java/com/javanauta/taskscheduler/infrastructure/client/UserClient.java +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/client/UserClient.java @@ -9,6 +9,6 @@ @FeignClient(name = "user", url = "${user.url}") public interface UserClient { - @GetMapping + @GetMapping("/user") UserDTO getUserByEmail(@RequestParam("email") String email, @RequestHeader("Authorization") String token); } From 3ab0837829cfc9b0c56d76e5afb2a244f9a579b1 Mon Sep 17 00:00:00 2001 From: Enzo Bom Date: Tue, 30 Dec 2025 18:18:00 -0300 Subject: [PATCH 3/6] Implement method to save a task --- build.gradle | 3 ++ .../TaskSchedulerApplication.java | 2 ++ .../taskscheduler/business/TaskService.java | 34 +++++++++++++++++++ .../taskscheduler/business/dto/TaskDTO.java | 25 ++++++++++++++ .../taskscheduler/business/dto/UserDTO.java | 3 +- .../business/mapper/TaskConverter.java | 12 +++++++ .../controller/TaskController.java | 20 +++++++++++ .../infrastructure/entity/Task.java | 27 +++++++++++++++ .../enums/NotificationStatusEnum.java | 7 ++++ .../repository/TaskRepository.java | 9 +++++ .../security/JwtRequestFilter.java | 2 +- src/main/resources/application.properties | 4 +++ 12 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/javanauta/taskscheduler/business/TaskService.java create mode 100644 src/main/java/com/javanauta/taskscheduler/business/dto/TaskDTO.java create mode 100644 src/main/java/com/javanauta/taskscheduler/business/mapper/TaskConverter.java create mode 100644 src/main/java/com/javanauta/taskscheduler/controller/TaskController.java create mode 100644 src/main/java/com/javanauta/taskscheduler/infrastructure/entity/Task.java create mode 100644 src/main/java/com/javanauta/taskscheduler/infrastructure/enums/NotificationStatusEnum.java create mode 100644 src/main/java/com/javanauta/taskscheduler/infrastructure/repository/TaskRepository.java diff --git a/build.gradle b/build.gradle index 1177756..e3b7309 100644 --- a/build.gradle +++ b/build.gradle @@ -34,10 +34,13 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-webmvc' implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' implementation 'io.jsonwebtoken:jjwt-api:0.13.0' + implementation 'org.mapstruct:mapstruct:1.6.3' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' + annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0' testImplementation 'org.springframework.boot:spring-boot-starter-data-mongodb-test' testImplementation 'org.springframework.boot:spring-boot-starter-security-test' diff --git a/src/main/java/com/javanauta/taskscheduler/TaskSchedulerApplication.java b/src/main/java/com/javanauta/taskscheduler/TaskSchedulerApplication.java index eae7aec..a5dc459 100644 --- a/src/main/java/com/javanauta/taskscheduler/TaskSchedulerApplication.java +++ b/src/main/java/com/javanauta/taskscheduler/TaskSchedulerApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication +@EnableFeignClients public class TaskSchedulerApplication { public static void main(String[] args) { diff --git a/src/main/java/com/javanauta/taskscheduler/business/TaskService.java b/src/main/java/com/javanauta/taskscheduler/business/TaskService.java new file mode 100644 index 0000000..865ae1f --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/business/TaskService.java @@ -0,0 +1,34 @@ +package com.javanauta.taskscheduler.business; + +import com.javanauta.taskscheduler.business.dto.TaskDTO; +import com.javanauta.taskscheduler.business.dto.UserDTO; +import com.javanauta.taskscheduler.business.mapper.TaskConverter; +import com.javanauta.taskscheduler.infrastructure.client.UserClient; +import com.javanauta.taskscheduler.infrastructure.entity.Task; +import com.javanauta.taskscheduler.infrastructure.enums.NotificationStatusEnum; +import com.javanauta.taskscheduler.infrastructure.repository.TaskRepository; +import com.javanauta.taskscheduler.infrastructure.security.JwtUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +public class TaskService { + + private final TaskRepository taskRepository; + private final TaskConverter taskConverter; + private final JwtUtil jwtUtil; + + public TaskDTO saveTask(String token, TaskDTO taskDTO) { + + taskDTO.setUserEmail(jwtUtil.extractUsername(token.substring(7))); + taskDTO.setCreationDateTime(LocalDateTime.now()); + taskDTO.setNotificationStatusEnum(NotificationStatusEnum.PENDING); + + Task task = taskConverter.toTask(taskDTO); + + return taskConverter.toTaskDTO(taskRepository.save(task)); + } +} diff --git a/src/main/java/com/javanauta/taskscheduler/business/dto/TaskDTO.java b/src/main/java/com/javanauta/taskscheduler/business/dto/TaskDTO.java new file mode 100644 index 0000000..8ecd307 --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/business/dto/TaskDTO.java @@ -0,0 +1,25 @@ +package com.javanauta.taskscheduler.business.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.javanauta.taskscheduler.infrastructure.enums.NotificationStatusEnum; +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class TaskDTO { + + private String id; + private String name; + private String description; + private LocalDateTime creationDateTime; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss") + private LocalDateTime dueDateTime; + private String userEmail; + private LocalDateTime modificationDateTime; + private NotificationStatusEnum notificationStatusEnum; +} diff --git a/src/main/java/com/javanauta/taskscheduler/business/dto/UserDTO.java b/src/main/java/com/javanauta/taskscheduler/business/dto/UserDTO.java index 11b71fe..c5059be 100644 --- a/src/main/java/com/javanauta/taskscheduler/business/dto/UserDTO.java +++ b/src/main/java/com/javanauta/taskscheduler/business/dto/UserDTO.java @@ -2,14 +2,13 @@ import lombok.*; -import java.util.List; - @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder public class UserDTO { + private String email; private String password; } diff --git a/src/main/java/com/javanauta/taskscheduler/business/mapper/TaskConverter.java b/src/main/java/com/javanauta/taskscheduler/business/mapper/TaskConverter.java new file mode 100644 index 0000000..1f8c76e --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/business/mapper/TaskConverter.java @@ -0,0 +1,12 @@ +package com.javanauta.taskscheduler.business.mapper; + +import com.javanauta.taskscheduler.business.dto.TaskDTO; +import com.javanauta.taskscheduler.infrastructure.entity.Task; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface TaskConverter { + + Task toTask(TaskDTO taskDTO); + TaskDTO toTaskDTO(Task task); +} diff --git a/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java b/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java new file mode 100644 index 0000000..2c91908 --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java @@ -0,0 +1,20 @@ +package com.javanauta.taskscheduler.controller; + +import com.javanauta.taskscheduler.business.TaskService; +import com.javanauta.taskscheduler.business.dto.TaskDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/task") +@RequiredArgsConstructor +public class TaskController { + + private final TaskService taskService; + + @PostMapping + public ResponseEntity saveTask(@RequestBody TaskDTO taskDTO, @RequestHeader("Authorization") String token) { + return ResponseEntity.ok(taskService.saveTask(token, taskDTO)); + } +} diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/entity/Task.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/entity/Task.java new file mode 100644 index 0000000..b2cd884 --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/entity/Task.java @@ -0,0 +1,27 @@ +package com.javanauta.taskscheduler.infrastructure.entity; + +import com.javanauta.taskscheduler.infrastructure.enums.NotificationStatusEnum; +import lombok.*; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.time.LocalDateTime; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Document("task") +public class Task { + + @Id + private String id; + private String name; + private String description; + private LocalDateTime creationDateTime; + private LocalDateTime dueDateTime; + private String userEmail; + private LocalDateTime modificationDateTime; + private NotificationStatusEnum notificationStatusEnum; +} diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/enums/NotificationStatusEnum.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/enums/NotificationStatusEnum.java new file mode 100644 index 0000000..093d896 --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/enums/NotificationStatusEnum.java @@ -0,0 +1,7 @@ +package com.javanauta.taskscheduler.infrastructure.enums; + +public enum NotificationStatusEnum { + PENDING, + NOTIFIED, + CANCELED +} diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/repository/TaskRepository.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/repository/TaskRepository.java new file mode 100644 index 0000000..4cd9845 --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/repository/TaskRepository.java @@ -0,0 +1,9 @@ +package com.javanauta.taskscheduler.infrastructure.repository; + +import com.javanauta.taskscheduler.infrastructure.entity.Task; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TaskRepository extends MongoRepository { +} diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/security/JwtRequestFilter.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/security/JwtRequestFilter.java index 13f665f..30d8ce7 100644 --- a/src/main/java/com/javanauta/taskscheduler/infrastructure/security/JwtRequestFilter.java +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/security/JwtRequestFilter.java @@ -42,7 +42,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse // If the username is not null and the user is not yet authenticated if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { // Loads user details from the username - UserDetails userDetails = userDetailsService.loadUserDetails(username, token); + UserDetails userDetails = userDetailsService.loadUserDetails(username, authorizationHeader); // Validates the JWT token if (jwtUtil.validateToken(token, username)) { // Creates an authentication object with user information diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2ad97a1..ce336be 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,6 @@ spring.application.name=task-scheduler +spring.data.mongodb.uri=mongodb://localhost:27017/task_scheduler_db + user.url=localhost:8080 + +server.port=8081 From 74e9245640ded437f54a16827169a3d281bf6c78 Mon Sep 17 00:00:00 2001 From: Enzo Bom Date: Wed, 14 Jan 2026 15:08:50 -0300 Subject: [PATCH 4/6] Implement method to get tasks by period --- .../javanauta/taskscheduler/business/TaskService.java | 6 ++++++ .../taskscheduler/business/mapper/TaskConverter.java | 4 ++++ .../taskscheduler/controller/TaskController.java | 11 +++++++++++ .../infrastructure/repository/TaskRepository.java | 4 ++++ src/main/resources/application.properties | 3 ++- 5 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/javanauta/taskscheduler/business/TaskService.java b/src/main/java/com/javanauta/taskscheduler/business/TaskService.java index 865ae1f..c7726bf 100644 --- a/src/main/java/com/javanauta/taskscheduler/business/TaskService.java +++ b/src/main/java/com/javanauta/taskscheduler/business/TaskService.java @@ -11,7 +11,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; @Service @RequiredArgsConstructor @@ -31,4 +33,8 @@ public TaskDTO saveTask(String token, TaskDTO taskDTO) { return taskConverter.toTaskDTO(taskRepository.save(task)); } + + public List findTaskByTimePeriod(LocalDateTime initialDateTime, LocalDateTime finalDateTime) { + return taskConverter.toTaskDTOList(taskRepository.findByDueDateTimeBetween(initialDateTime, finalDateTime)); + } } diff --git a/src/main/java/com/javanauta/taskscheduler/business/mapper/TaskConverter.java b/src/main/java/com/javanauta/taskscheduler/business/mapper/TaskConverter.java index 1f8c76e..3cbb472 100644 --- a/src/main/java/com/javanauta/taskscheduler/business/mapper/TaskConverter.java +++ b/src/main/java/com/javanauta/taskscheduler/business/mapper/TaskConverter.java @@ -4,9 +4,13 @@ import com.javanauta.taskscheduler.infrastructure.entity.Task; import org.mapstruct.Mapper; +import java.util.List; + @Mapper(componentModel = "spring") public interface TaskConverter { Task toTask(TaskDTO taskDTO); TaskDTO toTaskDTO(Task task); + List toTaskList(List taskDTOList); + List toTaskDTOList(List taskList); } diff --git a/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java b/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java index 2c91908..c329ec5 100644 --- a/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java +++ b/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java @@ -3,9 +3,15 @@ import com.javanauta.taskscheduler.business.TaskService; import com.javanauta.taskscheduler.business.dto.TaskDTO; import lombok.RequiredArgsConstructor; +import org.apache.coyote.Response; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; +import java.util.List; + @RestController @RequestMapping("/task") @RequiredArgsConstructor @@ -17,4 +23,9 @@ public class TaskController { public ResponseEntity saveTask(@RequestBody TaskDTO taskDTO, @RequestHeader("Authorization") String token) { return ResponseEntity.ok(taskService.saveTask(token, taskDTO)); } + + @GetMapping("/events") + public ResponseEntity> findTaskListByPeriod(@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime initialDateTime, @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime finalDateTime) { + return ResponseEntity.ok(taskService.findTaskByTimePeriod(initialDateTime, finalDateTime)); + } } diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/repository/TaskRepository.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/repository/TaskRepository.java index 4cd9845..7eb26ff 100644 --- a/src/main/java/com/javanauta/taskscheduler/infrastructure/repository/TaskRepository.java +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/repository/TaskRepository.java @@ -4,6 +4,10 @@ import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; +import java.util.List; + @Repository public interface TaskRepository extends MongoRepository { + List findByDueDateTimeBetween(LocalDateTime initialDateTime, LocalDateTime finalDateTime); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ce336be..7705e91 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,6 @@ spring.application.name=task-scheduler -spring.data.mongodb.uri=mongodb://localhost:27017/task_scheduler_db +spring.data.mongodb.uri=mongodb://localhost:27017 +spring.data.mongodb.database=task_scheduler_db user.url=localhost:8080 From 0778bae87efeb2fd151177aad2ab3c3ec6dc4ee8 Mon Sep 17 00:00:00 2001 From: Enzo Bom Date: Wed, 14 Jan 2026 16:00:39 -0300 Subject: [PATCH 5/6] Implement method to get tasks by user email --- .../com/javanauta/taskscheduler/business/TaskService.java | 7 ++++--- .../com/javanauta/taskscheduler/business/dto/TaskDTO.java | 2 ++ .../javanauta/taskscheduler/controller/TaskController.java | 5 +++++ .../infrastructure/repository/TaskRepository.java | 1 + 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/javanauta/taskscheduler/business/TaskService.java b/src/main/java/com/javanauta/taskscheduler/business/TaskService.java index c7726bf..59a32ef 100644 --- a/src/main/java/com/javanauta/taskscheduler/business/TaskService.java +++ b/src/main/java/com/javanauta/taskscheduler/business/TaskService.java @@ -1,9 +1,7 @@ package com.javanauta.taskscheduler.business; import com.javanauta.taskscheduler.business.dto.TaskDTO; -import com.javanauta.taskscheduler.business.dto.UserDTO; import com.javanauta.taskscheduler.business.mapper.TaskConverter; -import com.javanauta.taskscheduler.infrastructure.client.UserClient; import com.javanauta.taskscheduler.infrastructure.entity.Task; import com.javanauta.taskscheduler.infrastructure.enums.NotificationStatusEnum; import com.javanauta.taskscheduler.infrastructure.repository.TaskRepository; @@ -11,7 +9,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -37,4 +34,8 @@ public TaskDTO saveTask(String token, TaskDTO taskDTO) { public List findTaskByTimePeriod(LocalDateTime initialDateTime, LocalDateTime finalDateTime) { return taskConverter.toTaskDTOList(taskRepository.findByDueDateTimeBetween(initialDateTime, finalDateTime)); } + + public List findTaskByUserEmail(String token) { + return taskConverter.toTaskDTOList(taskRepository.findByUserEmail(jwtUtil.extractUsername(token.substring(7)))); + } } diff --git a/src/main/java/com/javanauta/taskscheduler/business/dto/TaskDTO.java b/src/main/java/com/javanauta/taskscheduler/business/dto/TaskDTO.java index 8ecd307..a0ef054 100644 --- a/src/main/java/com/javanauta/taskscheduler/business/dto/TaskDTO.java +++ b/src/main/java/com/javanauta/taskscheduler/business/dto/TaskDTO.java @@ -16,10 +16,12 @@ public class TaskDTO { private String id; private String name; private String description; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss") private LocalDateTime creationDateTime; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss") private LocalDateTime dueDateTime; private String userEmail; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss") private LocalDateTime modificationDateTime; private NotificationStatusEnum notificationStatusEnum; } diff --git a/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java b/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java index c329ec5..4806073 100644 --- a/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java +++ b/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java @@ -28,4 +28,9 @@ public ResponseEntity saveTask(@RequestBody TaskDTO taskDTO, @RequestHe public ResponseEntity> findTaskListByPeriod(@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime initialDateTime, @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime finalDateTime) { return ResponseEntity.ok(taskService.findTaskByTimePeriod(initialDateTime, finalDateTime)); } + + @GetMapping + public ResponseEntity> findTaskListByUserEmail(@RequestHeader("Authorization") String token) { + return ResponseEntity.ok(taskService.findTaskByUserEmail(token)); + } } diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/repository/TaskRepository.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/repository/TaskRepository.java index 7eb26ff..ec4fbd6 100644 --- a/src/main/java/com/javanauta/taskscheduler/infrastructure/repository/TaskRepository.java +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/repository/TaskRepository.java @@ -10,4 +10,5 @@ @Repository public interface TaskRepository extends MongoRepository { List findByDueDateTimeBetween(LocalDateTime initialDateTime, LocalDateTime finalDateTime); + List findByUserEmail(String userEmail); } From bb130af517840e8035a198bf54ca2a31b468a306 Mon Sep 17 00:00:00 2001 From: Enzo Bom Date: Wed, 14 Jan 2026 19:49:15 -0300 Subject: [PATCH 6/6] Implement methods to delete and update tasks as well as to modify the task status --- .../taskscheduler/business/TaskService.java | 27 +++++++++++++++++++ .../business/mapper/TaskUpdateConverter.java | 13 +++++++++ .../controller/TaskController.java | 18 +++++++++++++ .../exception/ResourceNotFoundException.java | 8 ++++++ 4 files changed, 66 insertions(+) create mode 100644 src/main/java/com/javanauta/taskscheduler/business/mapper/TaskUpdateConverter.java create mode 100644 src/main/java/com/javanauta/taskscheduler/infrastructure/exception/ResourceNotFoundException.java diff --git a/src/main/java/com/javanauta/taskscheduler/business/TaskService.java b/src/main/java/com/javanauta/taskscheduler/business/TaskService.java index 59a32ef..aed3815 100644 --- a/src/main/java/com/javanauta/taskscheduler/business/TaskService.java +++ b/src/main/java/com/javanauta/taskscheduler/business/TaskService.java @@ -2,11 +2,15 @@ import com.javanauta.taskscheduler.business.dto.TaskDTO; import com.javanauta.taskscheduler.business.mapper.TaskConverter; +import com.javanauta.taskscheduler.business.mapper.TaskUpdateConverter; import com.javanauta.taskscheduler.infrastructure.entity.Task; import com.javanauta.taskscheduler.infrastructure.enums.NotificationStatusEnum; +import com.javanauta.taskscheduler.infrastructure.exception.ResourceNotFoundException; import com.javanauta.taskscheduler.infrastructure.repository.TaskRepository; import com.javanauta.taskscheduler.infrastructure.security.JwtUtil; import lombok.RequiredArgsConstructor; +import org.osgi.resource.Resource; +import org.springframework.security.core.parameters.P; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @@ -19,6 +23,7 @@ public class TaskService { private final TaskRepository taskRepository; private final TaskConverter taskConverter; private final JwtUtil jwtUtil; + private final TaskUpdateConverter taskUpdateConverter; public TaskDTO saveTask(String token, TaskDTO taskDTO) { @@ -38,4 +43,26 @@ public List findTaskByTimePeriod(LocalDateTime initialDateTime, LocalDa public List findTaskByUserEmail(String token) { return taskConverter.toTaskDTOList(taskRepository.findByUserEmail(jwtUtil.extractUsername(token.substring(7)))); } + + public void deleteTaskById(String id) { + if (taskRepository.existsById(id)) { + taskRepository.deleteById(id); + } else { + throw new ResourceNotFoundException(String.format("Task with ID '%s' not found.", id)); + } + } + + public TaskDTO modifyTaskStatusById (NotificationStatusEnum notificationStatusEnum, String id) { + Task task = taskRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(String.format("Task with ID '%s' not found.", id))); + task.setNotificationStatusEnum(notificationStatusEnum); + + return taskConverter.toTaskDTO(taskRepository.save(task)); + } + + public TaskDTO updateTaskById (TaskDTO taskDTO, String id) { + Task task = taskRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(String.format("Task with ID '%s' not found.", id))); + + taskUpdateConverter.updateTasks(taskDTO, task); + return taskConverter.toTaskDTO(taskRepository.save(task)); + } } diff --git a/src/main/java/com/javanauta/taskscheduler/business/mapper/TaskUpdateConverter.java b/src/main/java/com/javanauta/taskscheduler/business/mapper/TaskUpdateConverter.java new file mode 100644 index 0000000..15b0bfe --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/business/mapper/TaskUpdateConverter.java @@ -0,0 +1,13 @@ +package com.javanauta.taskscheduler.business.mapper; + +import com.javanauta.taskscheduler.business.dto.TaskDTO; +import com.javanauta.taskscheduler.infrastructure.entity.Task; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; + +@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface TaskUpdateConverter { + + void updateTasks(TaskDTO taskDTO, @MappingTarget Task task); +} diff --git a/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java b/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java index 4806073..809b23d 100644 --- a/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java +++ b/src/main/java/com/javanauta/taskscheduler/controller/TaskController.java @@ -2,6 +2,8 @@ import com.javanauta.taskscheduler.business.TaskService; import com.javanauta.taskscheduler.business.dto.TaskDTO; +import com.javanauta.taskscheduler.infrastructure.enums.NotificationStatusEnum; +import feign.Param; import lombok.RequiredArgsConstructor; import org.apache.coyote.Response; import org.springframework.data.mongodb.core.MongoTemplate; @@ -33,4 +35,20 @@ public ResponseEntity> findTaskListByPeriod(@RequestParam @DateTim public ResponseEntity> findTaskListByUserEmail(@RequestHeader("Authorization") String token) { return ResponseEntity.ok(taskService.findTaskByUserEmail(token)); } + + @DeleteMapping + public ResponseEntity deleteTaskById(@RequestParam("id") String id){ + taskService.deleteTaskById(id); + return ResponseEntity.ok().build(); + } + + @PatchMapping + public ResponseEntity modifyTaskStatusById(@RequestParam("status") NotificationStatusEnum notificationStatusEnum, @RequestParam("id") String id) { + return ResponseEntity.ok(taskService.modifyTaskStatusById(notificationStatusEnum, id)); + } + + @PutMapping + public ResponseEntity updateTaskById(@RequestBody TaskDTO taskDTO, @RequestParam("id") String id) { + return ResponseEntity.ok(taskService.updateTaskById(taskDTO, id)); + } } diff --git a/src/main/java/com/javanauta/taskscheduler/infrastructure/exception/ResourceNotFoundException.java b/src/main/java/com/javanauta/taskscheduler/infrastructure/exception/ResourceNotFoundException.java new file mode 100644 index 0000000..3b14c96 --- /dev/null +++ b/src/main/java/com/javanauta/taskscheduler/infrastructure/exception/ResourceNotFoundException.java @@ -0,0 +1,8 @@ +package com.javanauta.taskscheduler.infrastructure.exception; + +public class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException(String message) { + super(message); + } + public ResourceNotFoundException(String message, Throwable throwable) { super(message, throwable); } +}