Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,22 @@ 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'
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'
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.javanauta.taskscheduler.business;

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;
import java.util.List;

@Service
@RequiredArgsConstructor
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) {

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));
}

public List<TaskDTO> findTaskByTimePeriod(LocalDateTime initialDateTime, LocalDateTime finalDateTime) {
return taskConverter.toTaskDTOList(taskRepository.findByDueDateTimeBetween(initialDateTime, finalDateTime));
}

public List<TaskDTO> 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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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;
@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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.javanauta.taskscheduler.business.dto;

import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDTO {

private String email;
private String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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 java.util.List;

@Mapper(componentModel = "spring")
public interface TaskConverter {

Task toTask(TaskDTO taskDTO);
TaskDTO toTaskDTO(Task task);
List<Task> toTaskList(List<TaskDTO> taskDTOList);
List<TaskDTO> toTaskDTOList(List<Task> taskList);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.javanauta.taskscheduler.controller;

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;
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
public class TaskController {

private final TaskService taskService;

@PostMapping
public ResponseEntity<TaskDTO> saveTask(@RequestBody TaskDTO taskDTO, @RequestHeader("Authorization") String token) {
return ResponseEntity.ok(taskService.saveTask(token, taskDTO));
}

@GetMapping("/events")
public ResponseEntity<List<TaskDTO>> 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<List<TaskDTO>> findTaskListByUserEmail(@RequestHeader("Authorization") String token) {
return ResponseEntity.ok(taskService.findTaskByUserEmail(token));
}

@DeleteMapping
public ResponseEntity<Void> deleteTaskById(@RequestParam("id") String id){
taskService.deleteTaskById(id);
return ResponseEntity.ok().build();
}

@PatchMapping
public ResponseEntity<TaskDTO> modifyTaskStatusById(@RequestParam("status") NotificationStatusEnum notificationStatusEnum, @RequestParam("id") String id) {
return ResponseEntity.ok(taskService.modifyTaskStatusById(notificationStatusEnum, id));
}

@PutMapping
public ResponseEntity<TaskDTO> updateTaskById(@RequestBody TaskDTO taskDTO, @RequestParam("id") String id) {
return ResponseEntity.ok(taskService.updateTaskById(taskDTO, id));
}
}
Original file line number Diff line number Diff line change
@@ -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("/user")
UserDTO getUserByEmail(@RequestParam("email") String email, @RequestHeader("Authorization") String token);
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.javanauta.taskscheduler.infrastructure.enums;

public enum NotificationStatusEnum {
PENDING,
NOTIFIED,
CANCELED
}
Original file line number Diff line number Diff line change
@@ -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); }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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;

import java.time.LocalDateTime;
import java.util.List;

@Repository
public interface TaskRepository extends MongoRepository<Task, String> {
List<Task> findByDueDateTimeBetween(LocalDateTime initialDateTime, LocalDateTime finalDateTime);
List<Task> findByUserEmail(String userEmail);
}
Original file line number Diff line number Diff line change
@@ -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, authorizationHeader);
// 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);
}
}
Loading