From 89226b35d4bc838a5848f3bee3a1788f4edf674c Mon Sep 17 00:00:00 2001 From: pirangicharan Date: Sun, 21 May 2023 15:58:34 +0530 Subject: [PATCH 01/10] Create entities --- .../springbootcodingtest/entity/Project.java | 33 +++++++++ .../springbootcodingtest/entity/Task.java | 74 +++++++++++++++++++ .../springbootcodingtest/entity/User.java | 44 +++++++++++ 3 files changed, 151 insertions(+) create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Task.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java new file mode 100644 index 0000000..9f387dc --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java @@ -0,0 +1,33 @@ +package com.accenture.codingtest.springbootcodingtest.entity; + +import javax.persistence.*; +import javax.validation.constraints.NotBlank; +import java.util.UUID; + +@Entity +@Table(name = "projects") +public class Project { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private UUID id; + @Column(nullable = false,unique = true) + private String name; + + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Task.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Task.java new file mode 100644 index 0000000..6f030cd --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Task.java @@ -0,0 +1,74 @@ +package com.accenture.codingtest.springbootcodingtest.entity; + + +import javax.persistence.*; +import java.util.UUID; + +@Entity +@Table(name = "tasks") +public class Task { + + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private UUID id; + @Column(nullable = false) + private String title; + private String description; + @Column(nullable = false) + private String status; + @ManyToOne + @JoinColumn(name = "project_id",nullable = false) + private Project project; + @ManyToOne + @JoinColumn(name = "user_id",nullable = false) + private User user; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public Project getProject() { + return project; + } + + public void setProject(Project project) { + this.project = project; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java new file mode 100644 index 0000000..79aeeba --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java @@ -0,0 +1,44 @@ +package com.accenture.codingtest.springbootcodingtest.entity; + +import javax.persistence.*; +import java.util.UUID; + +@Entity +@Table(name = "users") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private UUID id; + + @Column(nullable = false,unique = true) + private String username; + @Column(nullable = false) + private String password; + + + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} From 44726cf9b363dc382464f803686b607b8cb5d911 Mon Sep 17 00:00:00 2001 From: pirangicharan Date: Sun, 21 May 2023 16:02:15 +0530 Subject: [PATCH 02/10] Create REST APIs --- .../controller/ProjectController.java | 65 +++++++++++++ .../controller/TaskController.java | 95 +++++++++++++++++++ .../controller/UserController.java | 87 +++++++++++++++++ .../repository/ProjectRepository.java | 10 ++ .../repository/TaskRepository.java | 10 ++ .../repository/UserRepository.java | 9 ++ 6 files changed, 276 insertions(+) create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/controller/TaskController.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/repository/ProjectRepository.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/repository/TaskRepository.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/repository/UserRepository.java diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java new file mode 100644 index 0000000..1639310 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java @@ -0,0 +1,65 @@ +package com.accenture.codingtest.springbootcodingtest.controller; + +import com.accenture.codingtest.springbootcodingtest.entity.Project; +import com.accenture.codingtest.springbootcodingtest.repository.ProjectRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping(value = "api/v1/projects") +public class ProjectController { + private final ProjectRepository projectRepository; + + @Autowired + public ProjectController(ProjectRepository projectRepository) { + this.projectRepository = projectRepository; + } + + private Project fetchProject(UUID projectId) { + Project project = projectRepository.findById(projectId) + .orElseThrow(() -> new RuntimeException("Project not found with Id: " + projectId)); + return project; + } + + @GetMapping + public ResponseEntity> getAllProjects() { + List projects = projectRepository.findAll(); + return ResponseEntity.ok(projects); + } + + @GetMapping("/{project_id}") + public ResponseEntity getProjectById(@PathVariable("project_id") UUID projectId) { + Project project = fetchProject(projectId); + return ResponseEntity.ok(project); + } + + @PostMapping + public ResponseEntity createProject(@RequestBody Project project){ + Project savedProject = projectRepository.save(project); + return ResponseEntity.status(HttpStatus.CREATED).body(savedProject); + } + + @PutMapping("/{project_id}") + public ResponseEntity updateProjectById(@PathVariable("project_id") UUID projectId, + @RequestBody Project project){ + Project foundProject = fetchProject(projectId); + + foundProject.setName(project.getName()); + + return ResponseEntity.ok(foundProject); + } + + @DeleteMapping("/{project_id}") + public ResponseEntity deleteProject(@PathVariable("project_id") UUID projectId){ + Project foundProject = fetchProject(projectId); + projectRepository.delete(foundProject); + return ResponseEntity.noContent().build(); + } + + +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/TaskController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/TaskController.java new file mode 100644 index 0000000..e6ee3d8 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/TaskController.java @@ -0,0 +1,95 @@ +package com.accenture.codingtest.springbootcodingtest.controller; + +import com.accenture.codingtest.springbootcodingtest.entity.Task; +import com.accenture.codingtest.springbootcodingtest.repository.TaskRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping(value = "api/v1/tasks") +public class TaskController { + private final TaskRepository taskRepository; + + @Autowired + public TaskController(TaskRepository taskRepository) { + this.taskRepository = taskRepository; + } + + private Task fetchTask(UUID taskId) { + Task task = taskRepository.findById(taskId) + .orElseThrow(() -> new RuntimeException("Task not found with Id: " + taskId)); + return task; + } + + @GetMapping + public ResponseEntity> getAllTasks() { + List taskList = taskRepository.findAll(); + return ResponseEntity.ok(taskList); + } + + @GetMapping("/{task_id}") + public ResponseEntity getTaskById(@PathVariable("task_id") UUID taskId) { + Task task = fetchTask(taskId); + return ResponseEntity.ok(task); + } + + @PostMapping + public ResponseEntity createTask(@RequestBody Task task) { + Task savedTask = taskRepository.save(task); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @PutMapping("/{task_id}") + public ResponseEntity updateTask(@PathVariable("task_id") UUID taskId, + @RequestBody Task task) { + Task foundTask = fetchTask(taskId); + + foundTask.setTitle(task.getTitle()); + foundTask.setDescription(task.getDescription()); + foundTask.setStatus(task.getStatus()); + foundTask.setProject(task.getProject()); + foundTask.setUser(task.getUser()); + + Task updatedTask = taskRepository.save(foundTask); + return ResponseEntity.ok(updatedTask); + } + + @PatchMapping("/{task_id}") + public ResponseEntity patchTask(@PathVariable("task_id") UUID taskId, + @RequestBody Task task) { + Task foundTask = fetchTask(taskId); + + if (task.getTitle() != null) { + foundTask.setTitle(task.getTitle()); + } + if (task.getDescription() != null) { + foundTask.setDescription(task.getDescription()); + } + if (task.getStatus() != null) { + foundTask.setStatus(task.getStatus()); + } + if (task.getProject() != null) { + foundTask.setProject(task.getProject()); + } + if (task.getUser() != null) { + foundTask.setUser(task.getUser()); + } + + Task patchedTask = taskRepository.save(foundTask); + return ResponseEntity.ok(patchedTask); + } + + @DeleteMapping("/{task_id}") + public ResponseEntity deleteTask(@PathVariable("task_id") UUID taskId) { + Task foundTask = fetchTask(taskId); + taskRepository.delete(foundTask); + return ResponseEntity.noContent().build(); + } + + +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java new file mode 100644 index 0000000..19d0284 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java @@ -0,0 +1,87 @@ +package com.accenture.codingtest.springbootcodingtest.controller; + + +import com.accenture.codingtest.springbootcodingtest.entity.User; +import com.accenture.codingtest.springbootcodingtest.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping(value = "/api/v1/users") +public class UserController { + private final UserRepository userRepository; + + @Autowired + public UserController(UserRepository userRepository) { + this.userRepository = userRepository; + } + + private User fetchUser(UUID userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("User not found with userId: " + userId)); + return user; + } + + + @GetMapping + public ResponseEntity> getAllUsers(){ + List userList = userRepository.findAll(); + return ResponseEntity.ok(userList); + } + + @GetMapping("/{user_id}") + public ResponseEntity getUserById(@PathVariable("user_id") UUID userId) { + User user = fetchUser(userId); + return ResponseEntity.ok(user); + } + + + + @PostMapping + public ResponseEntity createUser(@RequestBody User user){ + User savedUser = userRepository.save(user); + return ResponseEntity.status(HttpStatus.CREATED).body(savedUser); + } + + @PutMapping("/{user_id}") + public ResponseEntity updateUser(@PathVariable("user_id") UUID userId, + @RequestBody User user){ + User foundUser = fetchUser(userId); + foundUser.setUsername(user.getUsername()); + foundUser.setPassword(user.getPassword()); + User updatedUser = userRepository.save(foundUser); + return ResponseEntity.ok(updatedUser); + } + + @PatchMapping("/{user_id}") + public ResponseEntity patchUser(@PathVariable("user_id") UUID userId, + @RequestBody User user){ + User foundUser = fetchUser(userId); + if (user.getUsername() != null){ + foundUser.setUsername(user.getUsername()); + } + if (user.getPassword() != null){ + foundUser.setPassword(user.getPassword()); + } + + User patchedUser = userRepository.save(foundUser); + return ResponseEntity.ok(patchedUser); + + } + + @DeleteMapping("/{user_id}") + public ResponseEntity deleteUser(@PathVariable("user_id") UUID userId){ + User foundUser = fetchUser(userId); + userRepository.delete(foundUser); + return ResponseEntity.noContent().build(); + } + + + + +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/ProjectRepository.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/ProjectRepository.java new file mode 100644 index 0000000..b4615e9 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/ProjectRepository.java @@ -0,0 +1,10 @@ +package com.accenture.codingtest.springbootcodingtest.repository; + +import com.accenture.codingtest.springbootcodingtest.entity.Project; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface ProjectRepository extends JpaRepository { + +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/TaskRepository.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/TaskRepository.java new file mode 100644 index 0000000..deb954b --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/TaskRepository.java @@ -0,0 +1,10 @@ +package com.accenture.codingtest.springbootcodingtest.repository; + +import com.accenture.codingtest.springbootcodingtest.entity.Task; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface TaskRepository extends JpaRepository { + +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/UserRepository.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/UserRepository.java new file mode 100644 index 0000000..59d3579 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/UserRepository.java @@ -0,0 +1,9 @@ +package com.accenture.codingtest.springbootcodingtest.repository; + +import com.accenture.codingtest.springbootcodingtest.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.UUID; + +public interface UserRepository extends JpaRepository { + +} From 1c0bb0433ac029753406e54f9c313242bbe39a5e Mon Sep 17 00:00:00 2001 From: pirangicharan Date: Sun, 21 May 2023 20:44:56 +0530 Subject: [PATCH 03/10] Implement features --- pom.xml | 29 ++++- .../SpringBootCodingTestApplication.java | 3 +- .../controller/ProjectController.java | 4 + .../controller/TaskController.java | 104 ++++++++++++------ .../controller/UserController.java | 3 + .../springbootcodingtest/entity/Project.java | 1 - .../springbootcodingtest/entity/User.java | 14 +++ .../springbootcodingtest/model/Role.java | 7 ++ .../model/TaskStatus.java | 8 ++ .../repository/UserRepository.java | 3 + .../security/SecurityConfig.java | 53 +++++++++ src/main/resources/application.properties | 7 ++ 12 files changed, 196 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/model/Role.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/model/TaskStatus.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/security/SecurityConfig.java diff --git a/pom.xml b/pom.xml index b0a6389..94e8ed9 100644 --- a/pom.xml +++ b/pom.xml @@ -21,13 +21,38 @@ org.springframework.boot spring-boot-starter - + + org.springframework.boot + spring-boot-starter-web + org.springframework.boot spring-boot-starter-test test - + + + + + com.h2database + h2 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-starter-security + + + diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/SpringBootCodingTestApplication.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/SpringBootCodingTestApplication.java index 14e2bbf..d0085bd 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/SpringBootCodingTestApplication.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/SpringBootCodingTestApplication.java @@ -3,8 +3,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; + @SpringBootApplication -public class SpringBootCodingTestApplication { +public class SpringBootCodingTestApplication{ public static void main(String[] args) { SpringApplication.run(SpringBootCodingTestApplication.class, args); diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java index 1639310..d18fc6c 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -13,6 +14,7 @@ @RestController @RequestMapping(value = "api/v1/projects") public class ProjectController { + private final ProjectRepository projectRepository; @Autowired @@ -27,6 +29,7 @@ private Project fetchProject(UUID projectId) { } @GetMapping + @PreAuthorize("hasAuthority('ADMIN') or hasAuthority('PRODUCT_OWNER')") public ResponseEntity> getAllProjects() { List projects = projectRepository.findAll(); return ResponseEntity.ok(projects); @@ -39,6 +42,7 @@ public ResponseEntity getProjectById(@PathVariable("project_id") UUID p } @PostMapping + @PreAuthorize("hasAuthority('PRODUCT_OWNER')") public ResponseEntity createProject(@RequestBody Project project){ Project savedProject = projectRepository.save(project); return ResponseEntity.status(HttpStatus.CREATED).body(savedProject); diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/TaskController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/TaskController.java index e6ee3d8..0d2ffc0 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/TaskController.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/TaskController.java @@ -1,32 +1,34 @@ package com.accenture.codingtest.springbootcodingtest.controller; import com.accenture.codingtest.springbootcodingtest.entity.Task; +import com.accenture.codingtest.springbootcodingtest.entity.User; +import com.accenture.codingtest.springbootcodingtest.model.TaskStatus; import com.accenture.codingtest.springbootcodingtest.repository.TaskRepository; +import com.accenture.codingtest.springbootcodingtest.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.UUID; @RestController -@RequestMapping(value = "api/v1/tasks") +@RequestMapping(value = "/api/v1/tasks") public class TaskController { private final TaskRepository taskRepository; + private final UserRepository userRepository; @Autowired - public TaskController(TaskRepository taskRepository) { + public TaskController(TaskRepository taskRepository, UserRepository userRepository) { this.taskRepository = taskRepository; - } - - private Task fetchTask(UUID taskId) { - Task task = taskRepository.findById(taskId) - .orElseThrow(() -> new RuntimeException("Task not found with Id: " + taskId)); - return task; + this.userRepository = userRepository; } @GetMapping + @PreAuthorize("hasRole('ADMIN') or hasAuthority('PRODUCT_OWNER')") public ResponseEntity> getAllTasks() { List taskList = taskRepository.findAll(); return ResponseEntity.ok(taskList); @@ -39,57 +41,87 @@ public ResponseEntity getTaskById(@PathVariable("task_id") UUID taskId) { } @PostMapping + @PreAuthorize("hasAuthority('PRODUCT_OWNER')") public ResponseEntity createTask(@RequestBody Task task) { + task.setStatus(TaskStatus.NOT_STARTED.toString()); Task savedTask = taskRepository.save(task); - return ResponseEntity.status(HttpStatus.CREATED).build(); + return ResponseEntity.status(HttpStatus.CREATED).body(savedTask); } @PutMapping("/{task_id}") + @PreAuthorize("hasAuthority('PRODUCT_OWNER')") public ResponseEntity updateTask(@PathVariable("task_id") UUID taskId, - @RequestBody Task task) { - Task foundTask = fetchTask(taskId); - - foundTask.setTitle(task.getTitle()); - foundTask.setDescription(task.getDescription()); - foundTask.setStatus(task.getStatus()); - foundTask.setProject(task.getProject()); - foundTask.setUser(task.getUser()); + @RequestBody Task updatedTask) { + Task task = fetchTask(taskId); + task.setTitle(updatedTask.getTitle()); + task.setDescription(updatedTask.getDescription()); + task.setStatus(updatedTask.getStatus()); + task.setProject(updatedTask.getProject()); + task.setUser(updatedTask.getUser()); - Task updatedTask = taskRepository.save(foundTask); - return ResponseEntity.ok(updatedTask); + Task savedTask = taskRepository.save(task); + return ResponseEntity.ok(savedTask); } @PatchMapping("/{task_id}") + @PreAuthorize("@securityService.isTaskOwner(#taskId, authentication)") public ResponseEntity patchTask(@PathVariable("task_id") UUID taskId, - @RequestBody Task task) { - Task foundTask = fetchTask(taskId); + @RequestBody Task updatedTask) { + Task task = fetchTask(taskId); - if (task.getTitle() != null) { - foundTask.setTitle(task.getTitle()); + if (updatedTask.getTitle() != null) { + task.setTitle(updatedTask.getTitle()); } - if (task.getDescription() != null) { - foundTask.setDescription(task.getDescription()); + if (updatedTask.getDescription() != null) { + task.setDescription(updatedTask.getDescription()); } - if (task.getStatus() != null) { - foundTask.setStatus(task.getStatus()); + if (updatedTask.getStatus() != null) { + task.setStatus(updatedTask.getStatus()); } - if (task.getProject() != null) { - foundTask.setProject(task.getProject()); + if (updatedTask.getProject() != null) { + task.setProject(updatedTask.getProject()); } - if (task.getUser() != null) { - foundTask.setUser(task.getUser()); + if (updatedTask.getUser() != null) { + task.setUser(updatedTask.getUser()); } - Task patchedTask = taskRepository.save(foundTask); - return ResponseEntity.ok(patchedTask); + Task savedTask = taskRepository.save(task); + return ResponseEntity.ok(savedTask); } @DeleteMapping("/{task_id}") - public ResponseEntity deleteTask(@PathVariable("task_id") UUID taskId) { - Task foundTask = fetchTask(taskId); - taskRepository.delete(foundTask); + @PreAuthorize("hasAuthority('PRODUCT_OWNER')") + public ResponseEntity deleteTask(@PathVariable("task_id") UUID taskId) { + Task task = fetchTask(taskId); + taskRepository.delete(task); return ResponseEntity.noContent().build(); } + @PutMapping("/{task_id}/status") + public ResponseEntity updateTaskStatus(@PathVariable("task_id") UUID taskId, + @RequestParam("status") TaskStatus status) { + Task task = fetchTask(taskId); + User currentUser = getCurrentUser(); + if (!task.getUser().equals(currentUser)) { + throw new IllegalArgumentException("You are not authorized to update the task status."); + } + + task.setStatus(status.toString()); + + Task savedTask = taskRepository.save(task); + return ResponseEntity.ok(savedTask); + } + + private Task fetchTask(UUID taskId) { + return taskRepository.findById(taskId) + .orElseThrow(() -> new RuntimeException("Task not found with ID: " + taskId)); + } + + private User getCurrentUser() { + String username = SecurityContextHolder.getContext().getAuthentication().getName(); + return userRepository.findByUsername(username) + .orElseThrow(() -> new IllegalStateException("No authenticated user found.")); + } } + diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java index 19d0284..8d3cb2f 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -13,7 +14,9 @@ @RestController @RequestMapping(value = "/api/v1/users") +@PreAuthorize("hasRole('ADMIN')") public class UserController { + private final UserRepository userRepository; @Autowired diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java index 9f387dc..20839da 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java @@ -1,7 +1,6 @@ package com.accenture.codingtest.springbootcodingtest.entity; import javax.persistence.*; -import javax.validation.constraints.NotBlank; import java.util.UUID; @Entity diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java index 79aeeba..bb59c44 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java @@ -1,6 +1,9 @@ package com.accenture.codingtest.springbootcodingtest.entity; +import com.accenture.codingtest.springbootcodingtest.model.Role; + import javax.persistence.*; +import java.util.Set; import java.util.UUID; @Entity @@ -16,7 +19,18 @@ public class User { @Column(nullable = false) private String password; + @ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER) + @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) + @Enumerated(EnumType.STRING) + private Set roles; + + public Set getRoles() { + return roles; + } + public void setRoles(Set roles) { + this.roles = roles; + } public UUID getId() { return id; diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/model/Role.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/model/Role.java new file mode 100644 index 0000000..a1a1ff8 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/model/Role.java @@ -0,0 +1,7 @@ +package com.accenture.codingtest.springbootcodingtest.model; + +public enum Role { + ADMIN, + PRODUCT_OWNER, + TEAM_MEMBER +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/model/TaskStatus.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/model/TaskStatus.java new file mode 100644 index 0000000..21e1af7 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/model/TaskStatus.java @@ -0,0 +1,8 @@ +package com.accenture.codingtest.springbootcodingtest.model; + +public enum TaskStatus { + NOT_STARTED, + IN_PROGRESS, + READY_FOR_TEST, + COMPLETED +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/UserRepository.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/UserRepository.java index 59d3579..8c9f80f 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/UserRepository.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/UserRepository.java @@ -2,8 +2,11 @@ import com.accenture.codingtest.springbootcodingtest.entity.User; import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; import java.util.UUID; public interface UserRepository extends JpaRepository { + Optional findByUsername(String userName); } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/security/SecurityConfig.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/security/SecurityConfig.java new file mode 100644 index 0000000..fadf023 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/security/SecurityConfig.java @@ -0,0 +1,53 @@ +package com.accenture.codingtest.springbootcodingtest.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.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +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.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests() + .antMatchers(HttpMethod.GET, "/api/v1/users/**").hasRole("ADMIN") + .antMatchers(HttpMethod.POST, "/api/v1/users/**").hasRole("ADMIN") + .antMatchers(HttpMethod.PUT, "/api/v1/users/**").hasRole("ADMIN") + .antMatchers(HttpMethod.PATCH, "/api/v1/users/**").hasRole("ADMIN") + .antMatchers(HttpMethod.DELETE, "/api/v1/users/**").hasRole("ADMIN") + .antMatchers(HttpMethod.POST, "/api/v1/projects/**").hasRole("PRODUCT_OWNER") + .antMatchers(HttpMethod.POST, "/api/v1/tasks/**").hasRole("PRODUCT_OWNER") + .antMatchers(HttpMethod.PUT, "/api/v1/tasks/**").hasRole("PRODUCT_OWNER") + .antMatchers(HttpMethod.PATCH, "/api/v1/tasks/**").hasRole("PRODUCT_OWNER") + .antMatchers(HttpMethod.DELETE, "/api/v1/tasks/**").hasRole("PRODUCT_OWNER") + .and().httpBasic() + .and().csrf().disable(); + } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .passwordEncoder(passwordEncoder()) // Use the password encoder here + .withUser("admin") + .password(passwordEncoder().encode("admin")) // Encode the password + .roles("ADMIN") + .and() + .withUser("product_owner") + .password(passwordEncoder().encode("password")) // Encode the password + .roles("PRODUCT_OWNER"); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b13789..7304338 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,8 @@ +# H2 Database +spring.h2.console.enabled=true +spring.datasource.url=jdbc:h2:mem:dcbapp +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect From f4b13f3f58fc49fc17977442d852818864ddc669 Mon Sep 17 00:00:00 2001 From: pirangicharan Date: Sun, 21 May 2023 21:55:12 +0530 Subject: [PATCH 04/10] Write test --- .../UserControllerTest.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/test/java/com/accenture/codingtest/springbootcodingtest/UserControllerTest.java diff --git a/src/test/java/com/accenture/codingtest/springbootcodingtest/UserControllerTest.java b/src/test/java/com/accenture/codingtest/springbootcodingtest/UserControllerTest.java new file mode 100644 index 0000000..ce25400 --- /dev/null +++ b/src/test/java/com/accenture/codingtest/springbootcodingtest/UserControllerTest.java @@ -0,0 +1,51 @@ +package com.accenture.codingtest.springbootcodingtest; + +import com.accenture.codingtest.springbootcodingtest.entity.User; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.*; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class UserControllerTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void testCreateUser() { + String createUserUrl = "/api/v1/users"; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBasicAuth("admin", "admin"); + + String requestBody = """ + { + "username":"Ram", + "password":"1234" + } + """; + HttpEntity requestEntity = new HttpEntity<>(requestBody, headers); + + URI uri = UriComponentsBuilder.fromUriString(createUserUrl).build().toUri(); + + ResponseEntity response = restTemplate.exchange( + uri, + HttpMethod.POST, + requestEntity, + User.class + ); + + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + User createdUser = response.getBody(); + assertEquals("Ram", createdUser.getUsername()); + } + + +} From c0d2c72a4d570395e3290ef491844c669c0ca40a Mon Sep 17 00:00:00 2001 From: pirangicharan Date: Sun, 21 May 2023 22:23:58 +0530 Subject: [PATCH 05/10] Implement pagination --- pom.xml | 10 +++++- .../controller/ProjectController.java | 31 ++++++++++++++++--- .../repository/ProjectRepository.java | 3 ++ .../UserControllerTest.java | 2 +- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 94e8ed9..9ad5ad6 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,15 @@ org.springframework.boot spring-boot-maven-plugin - + + org.apache.maven.plugins + maven-compiler-plugin + + 15 + 15 + + + diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java index d18fc6c..de7c275 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java @@ -3,9 +3,13 @@ import com.accenture.codingtest.springbootcodingtest.entity.Project; import com.accenture.codingtest.springbootcodingtest.repository.ProjectRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -30,8 +34,25 @@ private Project fetchProject(UUID projectId) { @GetMapping @PreAuthorize("hasAuthority('ADMIN') or hasAuthority('PRODUCT_OWNER')") - public ResponseEntity> getAllProjects() { - List projects = projectRepository.findAll(); + public ResponseEntity> getAllProjects( + @RequestParam(required = false) String q, + @RequestParam(defaultValue = "0") int pageIndex, + @RequestParam(defaultValue = "3") int pageSize, + @RequestParam(defaultValue = "name") String sortBy, + @RequestParam(defaultValue = "ASC") String sortDirection + ) { + Sort.Direction direction = Sort.Direction.ASC; + if (sortDirection.equalsIgnoreCase("DESC")) { + direction = Sort.Direction.DESC; + } + PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, Sort.by(direction, sortBy)); + Page projects; + + if (q != null) { + projects = projectRepository.findByNameContainingIgnoreCase(q, pageRequest); + } else { + projects = projectRepository.findAll(pageRequest); + } return ResponseEntity.ok(projects); } @@ -43,14 +64,14 @@ public ResponseEntity getProjectById(@PathVariable("project_id") UUID p @PostMapping @PreAuthorize("hasAuthority('PRODUCT_OWNER')") - public ResponseEntity createProject(@RequestBody Project project){ + public ResponseEntity createProject(@RequestBody Project project) { Project savedProject = projectRepository.save(project); return ResponseEntity.status(HttpStatus.CREATED).body(savedProject); } @PutMapping("/{project_id}") public ResponseEntity updateProjectById(@PathVariable("project_id") UUID projectId, - @RequestBody Project project){ + @RequestBody Project project) { Project foundProject = fetchProject(projectId); foundProject.setName(project.getName()); @@ -59,7 +80,7 @@ public ResponseEntity updateProjectById(@PathVariable("project_id") UUI } @DeleteMapping("/{project_id}") - public ResponseEntity deleteProject(@PathVariable("project_id") UUID projectId){ + public ResponseEntity deleteProject(@PathVariable("project_id") UUID projectId) { Project foundProject = fetchProject(projectId); projectRepository.delete(foundProject); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/ProjectRepository.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/ProjectRepository.java index b4615e9..d2839ce 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/ProjectRepository.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/ProjectRepository.java @@ -1,10 +1,13 @@ package com.accenture.codingtest.springbootcodingtest.repository; import com.accenture.codingtest.springbootcodingtest.entity.Project; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; import java.util.UUID; public interface ProjectRepository extends JpaRepository { + Page findByNameContainingIgnoreCase(String q, PageRequest pageRequest); } diff --git a/src/test/java/com/accenture/codingtest/springbootcodingtest/UserControllerTest.java b/src/test/java/com/accenture/codingtest/springbootcodingtest/UserControllerTest.java index ce25400..f3408ea 100644 --- a/src/test/java/com/accenture/codingtest/springbootcodingtest/UserControllerTest.java +++ b/src/test/java/com/accenture/codingtest/springbootcodingtest/UserControllerTest.java @@ -47,5 +47,5 @@ public void testCreateUser() { assertEquals("Ram", createdUser.getUsername()); } - + } From 7fc7d23bf4d623dd51f47db06fd1b0daf30a0d0b Mon Sep 17 00:00:00 2001 From: pirangicharan <132801921+pirangicharan@users.noreply.github.com> Date: Sun, 21 May 2023 22:35:27 +0530 Subject: [PATCH 06/10] Add files via upload Add postman collection --- ...g_boot_coding_test.postman_collection.json | 454 ++++++++++++++++++ 1 file changed, 454 insertions(+) create mode 100644 docs/spring_boot_coding_test.postman_collection.json diff --git a/docs/spring_boot_coding_test.postman_collection.json b/docs/spring_boot_coding_test.postman_collection.json new file mode 100644 index 0000000..4e64425 --- /dev/null +++ b/docs/spring_boot_coding_test.postman_collection.json @@ -0,0 +1,454 @@ +{ + "info": { + "_postman_id": "88a0d749-6f0e-4000-abc9-4a87de218946", + "name": "spring_boot_coding_test", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "27536794" + }, + "item": [ + { + "name": "Users", + "item": [ + { + "name": "GetAllUsers", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/users", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "users" + ] + } + }, + "response": [] + }, + { + "name": "CreateUser", + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "29207796-400d-4653-a5ca-5e5b5f8f6540", + "type": "string" + }, + { + "key": "username", + "value": "admin", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"username\": \"Ram\",\r\n \"password\": \"abcd\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/api/v1/users", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "users" + ] + } + }, + "response": [] + }, + { + "name": "UpdateUserById", + "request": { + "method": "PUT", + "header": [], + "url": { + "raw": "http://localhost:8080/api/v1/users/0f2f0a79-0640-4cca-8b07-e683c70e33fc", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "users", + "0f2f0a79-0640-4cca-8b07-e683c70e33fc" + ] + } + }, + "response": [] + }, + { + "name": "FetchUserById", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/users/192a2472-9d91-4016-99fb-9b0882d916b3", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "users", + "192a2472-9d91-4016-99fb-9b0882d916b3" + ] + } + }, + "response": [] + }, + { + "name": "PatchUserById", + "request": { + "method": "PATCH", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/users/192a2472-9d91-4016-99fb-9b0882d916b3", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "users", + "192a2472-9d91-4016-99fb-9b0882d916b3" + ] + } + }, + "response": [] + }, + { + "name": "DeleteUser", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/users/192a2472-9d91-4016-99fb-9b0882d916b3", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "users", + "192a2472-9d91-4016-99fb-9b0882d916b3" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Projects", + "item": [ + { + "name": "GetAllProjects", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/projects", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "projects" + ] + } + }, + "response": [] + }, + { + "name": "CreateProject", + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "0e9a0d44-f14c-4515-ba03-449b0223dcce", + "type": "string" + }, + { + "key": "username", + "value": "product_owner", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\":\"Project1\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/api/v1/projects", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "projects" + ] + } + }, + "response": [] + }, + { + "name": "UpdateOrPatchProjectById", + "request": { + "method": "PUT", + "header": [], + "url": { + "raw": "http://localhost:8080/api/v1/projects/0f2f0a79-0640-4cca-8b07-e683c70e33fc", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "projects", + "0f2f0a79-0640-4cca-8b07-e683c70e33fc" + ] + } + }, + "response": [] + }, + { + "name": "FetchProjectById", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/projects/192a2472-9d91-4016-99fb-9b0882d916b3", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "projects", + "192a2472-9d91-4016-99fb-9b0882d916b3" + ] + } + }, + "response": [] + }, + { + "name": "DeleteProject", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/projects/192a2472-9d91-4016-99fb-9b0882d916b3", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "projects", + "192a2472-9d91-4016-99fb-9b0882d916b3" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Tasks", + "item": [ + { + "name": "GetAllTasks", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/tasks", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "tasks" + ] + } + }, + "response": [] + }, + { + "name": "CreateTask", + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "0e9a0d44-f14c-4515-ba03-449b0223dcce", + "type": "string" + }, + { + "key": "username", + "value": "product_owner", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"title\" : \"Eating\",\r\n \"description\" : \"Eating with Teeth\",\r\n \"status\" : \"Work In Progress\",\r\n \"project\":{\r\n \"id\" : \"e8c57a39-fe41-4523-977c-2b97e21e936c\"\r\n },\r\n \"user\" : {\r\n \"id\" : \"e960b50f-1f95-4dcf-b8f6-1522dcf051bb\"\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/api/v1/tasks", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "tasks" + ] + } + }, + "response": [] + }, + { + "name": "UpdateTaskById", + "request": { + "method": "PUT", + "header": [], + "url": { + "raw": "http://localhost:8080/api/v1/tasks/0f2f0a79-0640-4cca-8b07-e683c70e33fc", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "tasks", + "0f2f0a79-0640-4cca-8b07-e683c70e33fc" + ] + } + }, + "response": [] + }, + { + "name": "FetchTaskById", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/tasks/192a2472-9d91-4016-99fb-9b0882d916b3", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "tasks", + "192a2472-9d91-4016-99fb-9b0882d916b3" + ] + } + }, + "response": [] + }, + { + "name": "PatchTaskById", + "request": { + "method": "PATCH", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/tasks/192a2472-9d91-4016-99fb-9b0882d916b3", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "tasks", + "192a2472-9d91-4016-99fb-9b0882d916b3" + ] + } + }, + "response": [] + }, + { + "name": "DeleteTask", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/tasks/192a2472-9d91-4016-99fb-9b0882d916b3", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "tasks", + "192a2472-9d91-4016-99fb-9b0882d916b3" + ] + } + }, + "response": [] + } + ] + } + ] +} \ No newline at end of file From ab84c74b2b356f715d9f7b5db7d4b0eee10355ee Mon Sep 17 00:00:00 2001 From: pirangicharan Date: Sat, 27 May 2023 12:24:15 +0530 Subject: [PATCH 07/10] updated code for first phase --- pom.xml | 5 +- .../controller/ProjectController.java | 57 +++------- .../controller/TaskController.java | 91 ++++----------- .../controller/UserController.java | 45 ++------ .../springbootcodingtest/entity/Task.java | 9 +- .../springbootcodingtest/entity/User.java | 14 ++- .../security/SecurityConfig.java | 53 --------- .../service/ProjectService.java | 51 +++++++++ .../service/TaskService.java | 105 ++++++++++++++++++ .../service/UserService.java | 62 +++++++++++ 10 files changed, 280 insertions(+), 212 deletions(-) delete mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/security/SecurityConfig.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/service/ProjectService.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/service/TaskService.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/service/UserService.java diff --git a/pom.xml b/pom.xml index 9ad5ad6..b98dbea 100644 --- a/pom.xml +++ b/pom.xml @@ -47,10 +47,7 @@ runtime true - - org.springframework.boot - spring-boot-starter-security - + diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java index de7c275..21b7751 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java @@ -1,15 +1,10 @@ package com.accenture.codingtest.springbootcodingtest.controller; import com.accenture.codingtest.springbootcodingtest.entity.Project; -import com.accenture.codingtest.springbootcodingtest.repository.ProjectRepository; +import com.accenture.codingtest.springbootcodingtest.service.ProjectService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -19,70 +14,46 @@ @RequestMapping(value = "api/v1/projects") public class ProjectController { - private final ProjectRepository projectRepository; + private final ProjectService projectService; @Autowired - public ProjectController(ProjectRepository projectRepository) { - this.projectRepository = projectRepository; + public ProjectController(ProjectService projectService) { + this.projectService = projectService; } - private Project fetchProject(UUID projectId) { - Project project = projectRepository.findById(projectId) - .orElseThrow(() -> new RuntimeException("Project not found with Id: " + projectId)); - return project; - } - @GetMapping - @PreAuthorize("hasAuthority('ADMIN') or hasAuthority('PRODUCT_OWNER')") - public ResponseEntity> getAllProjects( - @RequestParam(required = false) String q, - @RequestParam(defaultValue = "0") int pageIndex, - @RequestParam(defaultValue = "3") int pageSize, - @RequestParam(defaultValue = "name") String sortBy, - @RequestParam(defaultValue = "ASC") String sortDirection - ) { - Sort.Direction direction = Sort.Direction.ASC; - if (sortDirection.equalsIgnoreCase("DESC")) { - direction = Sort.Direction.DESC; - } - PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, Sort.by(direction, sortBy)); - Page projects; - if (q != null) { - projects = projectRepository.findByNameContainingIgnoreCase(q, pageRequest); - } else { - projects = projectRepository.findAll(pageRequest); - } + @GetMapping + //@PreAuthorize("hasRole('ADMIN') or hasRole('PRODUCT_OWNER')") + public ResponseEntity> getAllProjects() { + List projects = projectService.findAll(); return ResponseEntity.ok(projects); } @GetMapping("/{project_id}") public ResponseEntity getProjectById(@PathVariable("project_id") UUID projectId) { - Project project = fetchProject(projectId); + Project project = projectService.findById(projectId); return ResponseEntity.ok(project); } @PostMapping - @PreAuthorize("hasAuthority('PRODUCT_OWNER')") + //@PreAuthorize("hasRole('PRODUCT_OWNER')") public ResponseEntity createProject(@RequestBody Project project) { - Project savedProject = projectRepository.save(project); + Project savedProject = projectService.save(project); return ResponseEntity.status(HttpStatus.CREATED).body(savedProject); } @PutMapping("/{project_id}") public ResponseEntity updateProjectById(@PathVariable("project_id") UUID projectId, @RequestBody Project project) { - Project foundProject = fetchProject(projectId); - - foundProject.setName(project.getName()); + Project updated = projectService.updateProject(projectId,project); - return ResponseEntity.ok(foundProject); + return ResponseEntity.ok(updated); } @DeleteMapping("/{project_id}") public ResponseEntity deleteProject(@PathVariable("project_id") UUID projectId) { - Project foundProject = fetchProject(projectId); - projectRepository.delete(foundProject); + projectService.delete(projectId); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/TaskController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/TaskController.java index 0d2ffc0..dd082ce 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/TaskController.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/TaskController.java @@ -1,15 +1,11 @@ package com.accenture.codingtest.springbootcodingtest.controller; import com.accenture.codingtest.springbootcodingtest.entity.Task; -import com.accenture.codingtest.springbootcodingtest.entity.User; import com.accenture.codingtest.springbootcodingtest.model.TaskStatus; -import com.accenture.codingtest.springbootcodingtest.repository.TaskRepository; -import com.accenture.codingtest.springbootcodingtest.repository.UserRepository; +import com.accenture.codingtest.springbootcodingtest.service.TaskService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -18,110 +14,65 @@ @RestController @RequestMapping(value = "/api/v1/tasks") public class TaskController { - private final TaskRepository taskRepository; - private final UserRepository userRepository; + private final TaskService taskService; @Autowired - public TaskController(TaskRepository taskRepository, UserRepository userRepository) { - this.taskRepository = taskRepository; - this.userRepository = userRepository; + public TaskController(TaskService taskService) { + this.taskService = taskService; } @GetMapping - @PreAuthorize("hasRole('ADMIN') or hasAuthority('PRODUCT_OWNER')") public ResponseEntity> getAllTasks() { - List taskList = taskRepository.findAll(); + List taskList = taskService.findAll(); return ResponseEntity.ok(taskList); } @GetMapping("/{task_id}") public ResponseEntity getTaskById(@PathVariable("task_id") UUID taskId) { - Task task = fetchTask(taskId); + Task task = taskService.findById(taskId); return ResponseEntity.ok(task); } @PostMapping - @PreAuthorize("hasAuthority('PRODUCT_OWNER')") public ResponseEntity createTask(@RequestBody Task task) { - task.setStatus(TaskStatus.NOT_STARTED.toString()); - Task savedTask = taskRepository.save(task); + task.setStatus(TaskStatus.NOT_STARTED); + Task savedTask = taskService.save(task); return ResponseEntity.status(HttpStatus.CREATED).body(savedTask); } @PutMapping("/{task_id}") - @PreAuthorize("hasAuthority('PRODUCT_OWNER')") public ResponseEntity updateTask(@PathVariable("task_id") UUID taskId, @RequestBody Task updatedTask) { - Task task = fetchTask(taskId); - task.setTitle(updatedTask.getTitle()); - task.setDescription(updatedTask.getDescription()); - task.setStatus(updatedTask.getStatus()); - task.setProject(updatedTask.getProject()); - task.setUser(updatedTask.getUser()); - - Task savedTask = taskRepository.save(task); - return ResponseEntity.ok(savedTask); + Task task = taskService.updateTask(taskId,updatedTask); + return ResponseEntity.ok(task); } + @PatchMapping("/{task_id}") - @PreAuthorize("@securityService.isTaskOwner(#taskId, authentication)") public ResponseEntity patchTask(@PathVariable("task_id") UUID taskId, @RequestBody Task updatedTask) { - Task task = fetchTask(taskId); - - if (updatedTask.getTitle() != null) { - task.setTitle(updatedTask.getTitle()); - } - if (updatedTask.getDescription() != null) { - task.setDescription(updatedTask.getDescription()); - } - if (updatedTask.getStatus() != null) { - task.setStatus(updatedTask.getStatus()); - } - if (updatedTask.getProject() != null) { - task.setProject(updatedTask.getProject()); - } - if (updatedTask.getUser() != null) { - task.setUser(updatedTask.getUser()); - } - - Task savedTask = taskRepository.save(task); - return ResponseEntity.ok(savedTask); + Task task = taskService.patchTask(taskId,updatedTask); + + return ResponseEntity.ok(task); } @DeleteMapping("/{task_id}") - @PreAuthorize("hasAuthority('PRODUCT_OWNER')") public ResponseEntity deleteTask(@PathVariable("task_id") UUID taskId) { - Task task = fetchTask(taskId); - taskRepository.delete(task); + + taskService.delete(taskId); return ResponseEntity.noContent().build(); } - @PutMapping("/{task_id}/status") + + @PatchMapping("/{task_id}/status") public ResponseEntity updateTaskStatus(@PathVariable("task_id") UUID taskId, @RequestParam("status") TaskStatus status) { - Task task = fetchTask(taskId); - User currentUser = getCurrentUser(); - - if (!task.getUser().equals(currentUser)) { - throw new IllegalArgumentException("You are not authorized to update the task status."); - } + Task task = taskService.updateTaskStatus(taskId,status); + return ResponseEntity.ok(task); + } - task.setStatus(status.toString()); - Task savedTask = taskRepository.save(task); - return ResponseEntity.ok(savedTask); - } - private Task fetchTask(UUID taskId) { - return taskRepository.findById(taskId) - .orElseThrow(() -> new RuntimeException("Task not found with ID: " + taskId)); - } - private User getCurrentUser() { - String username = SecurityContextHolder.getContext().getAuthentication().getName(); - return userRepository.findByUsername(username) - .orElseThrow(() -> new IllegalStateException("No authenticated user found.")); - } } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java index 8d3cb2f..01f5440 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java @@ -2,11 +2,10 @@ import com.accenture.codingtest.springbootcodingtest.entity.User; -import com.accenture.codingtest.springbootcodingtest.repository.UserRepository; +import com.accenture.codingtest.springbootcodingtest.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -14,73 +13,53 @@ @RestController @RequestMapping(value = "/api/v1/users") -@PreAuthorize("hasRole('ADMIN')") +//@PreAuthorize("hasRole('ADMIN')") public class UserController { - private final UserRepository userRepository; + private final UserService userService; @Autowired - public UserController(UserRepository userRepository) { - this.userRepository = userRepository; + public UserController(UserService userService) { + this.userService = userService; } - private User fetchUser(UUID userId) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new RuntimeException("User not found with userId: " + userId)); - return user; - } - - @GetMapping public ResponseEntity> getAllUsers(){ - List userList = userRepository.findAll(); + List userList = userService.findAll(); return ResponseEntity.ok(userList); } @GetMapping("/{user_id}") public ResponseEntity getUserById(@PathVariable("user_id") UUID userId) { - User user = fetchUser(userId); + User user = userService.findById(userId); return ResponseEntity.ok(user); } - - @PostMapping public ResponseEntity createUser(@RequestBody User user){ - User savedUser = userRepository.save(user); + User savedUser = userService.save(user); return ResponseEntity.status(HttpStatus.CREATED).body(savedUser); } @PutMapping("/{user_id}") public ResponseEntity updateUser(@PathVariable("user_id") UUID userId, @RequestBody User user){ - User foundUser = fetchUser(userId); - foundUser.setUsername(user.getUsername()); - foundUser.setPassword(user.getPassword()); - User updatedUser = userRepository.save(foundUser); + + User updatedUser = userService.updateUser(userId,user); return ResponseEntity.ok(updatedUser); } @PatchMapping("/{user_id}") public ResponseEntity patchUser(@PathVariable("user_id") UUID userId, @RequestBody User user){ - User foundUser = fetchUser(userId); - if (user.getUsername() != null){ - foundUser.setUsername(user.getUsername()); - } - if (user.getPassword() != null){ - foundUser.setPassword(user.getPassword()); - } - - User patchedUser = userRepository.save(foundUser); + User patchedUser = userService.patchUser(userId,user); return ResponseEntity.ok(patchedUser); } @DeleteMapping("/{user_id}") public ResponseEntity deleteUser(@PathVariable("user_id") UUID userId){ - User foundUser = fetchUser(userId); - userRepository.delete(foundUser); + userService.delete(userId); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Task.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Task.java index 6f030cd..6bf6fa7 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Task.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Task.java @@ -1,6 +1,8 @@ package com.accenture.codingtest.springbootcodingtest.entity; +import com.accenture.codingtest.springbootcodingtest.model.TaskStatus; + import javax.persistence.*; import java.util.UUID; @@ -16,7 +18,8 @@ public class Task { private String title; private String description; @Column(nullable = false) - private String status; + @Enumerated(EnumType.STRING) + private TaskStatus status; @ManyToOne @JoinColumn(name = "project_id",nullable = false) private Project project; @@ -48,11 +51,11 @@ public void setDescription(String description) { this.description = description; } - public String getStatus() { + public TaskStatus getStatus() { return status; } - public void setStatus(String status) { + public void setStatus(TaskStatus status) { this.status = status; } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java index bb59c44..04b72e8 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java @@ -18,20 +18,22 @@ public class User { private String username; @Column(nullable = false) private String password; - +/* @ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER) @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) @Enumerated(EnumType.STRING) - private Set roles; + private Set role; - public Set getRoles() { - return roles; + public Set getRole() { + return role; } - public void setRoles(Set roles) { - this.roles = roles; + public void setRole(Set role) { + this.role = role; } + */ + public UUID getId() { return id; } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/security/SecurityConfig.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/security/SecurityConfig.java deleted file mode 100644 index fadf023..0000000 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/security/SecurityConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.accenture.codingtest.springbootcodingtest.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.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -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.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; - -@Configuration -@EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) -public class SecurityConfig extends WebSecurityConfigurerAdapter { - @Override - protected void configure(HttpSecurity http) throws Exception { - http.authorizeRequests() - .antMatchers(HttpMethod.GET, "/api/v1/users/**").hasRole("ADMIN") - .antMatchers(HttpMethod.POST, "/api/v1/users/**").hasRole("ADMIN") - .antMatchers(HttpMethod.PUT, "/api/v1/users/**").hasRole("ADMIN") - .antMatchers(HttpMethod.PATCH, "/api/v1/users/**").hasRole("ADMIN") - .antMatchers(HttpMethod.DELETE, "/api/v1/users/**").hasRole("ADMIN") - .antMatchers(HttpMethod.POST, "/api/v1/projects/**").hasRole("PRODUCT_OWNER") - .antMatchers(HttpMethod.POST, "/api/v1/tasks/**").hasRole("PRODUCT_OWNER") - .antMatchers(HttpMethod.PUT, "/api/v1/tasks/**").hasRole("PRODUCT_OWNER") - .antMatchers(HttpMethod.PATCH, "/api/v1/tasks/**").hasRole("PRODUCT_OWNER") - .antMatchers(HttpMethod.DELETE, "/api/v1/tasks/**").hasRole("PRODUCT_OWNER") - .and().httpBasic() - .and().csrf().disable(); - } - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.inMemoryAuthentication() - .passwordEncoder(passwordEncoder()) // Use the password encoder here - .withUser("admin") - .password(passwordEncoder().encode("admin")) // Encode the password - .roles("ADMIN") - .and() - .withUser("product_owner") - .password(passwordEncoder().encode("password")) // Encode the password - .roles("PRODUCT_OWNER"); - } - -} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/service/ProjectService.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/ProjectService.java new file mode 100644 index 0000000..3857ac7 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/ProjectService.java @@ -0,0 +1,51 @@ +package com.accenture.codingtest.springbootcodingtest.service; + +import com.accenture.codingtest.springbootcodingtest.entity.Project; +import com.accenture.codingtest.springbootcodingtest.entity.User; +import com.accenture.codingtest.springbootcodingtest.repository.ProjectRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Service +public class ProjectService { + + private final ProjectRepository projectRepository; + + @Autowired + public ProjectService(ProjectRepository projectRepository) { + this.projectRepository = projectRepository; + } + + + public Project findById(UUID projectId) { + Project project = projectRepository.findById(projectId) + .orElseThrow(() -> new RuntimeException("Project not found with Id: " + projectId)); + return project; + } + + public List findAll() { + return projectRepository.findAll(); + } + + public Project save(Project project) { + Project saved = projectRepository.save(project); + return saved; + } + + public Project updateProject(UUID projectId, Project project) { + Project fetch = findById(projectId); + fetch.setName(project.getName()); + return projectRepository.save(fetch); + } + + public void delete(UUID projectId){ + Project fetch = findById(projectId); + projectRepository.delete(fetch); + } + + +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/service/TaskService.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/TaskService.java new file mode 100644 index 0000000..d6ba56c --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/TaskService.java @@ -0,0 +1,105 @@ +package com.accenture.codingtest.springbootcodingtest.service; + +import com.accenture.codingtest.springbootcodingtest.entity.Project; +import com.accenture.codingtest.springbootcodingtest.entity.Task; +import com.accenture.codingtest.springbootcodingtest.entity.User; +import com.accenture.codingtest.springbootcodingtest.model.TaskStatus; +import com.accenture.codingtest.springbootcodingtest.repository.ProjectRepository; +import com.accenture.codingtest.springbootcodingtest.repository.TaskRepository; +import com.accenture.codingtest.springbootcodingtest.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; + +@Service +public class TaskService { + private final TaskRepository taskRepository; + private final UserService userService; + private final ProjectService projectService; + + @Autowired + public TaskService(TaskRepository taskRepository, UserService userService, ProjectService projectService) { + this.taskRepository = taskRepository; + this.userService = userService; + this.projectService = projectService; + } + + public Task findById(UUID taskId) { + return taskRepository.findById(taskId) + .orElseThrow(() -> new RuntimeException("Task not found with Id: " + taskId)); + } + + public List findAll() { + return taskRepository.findAll(); + } + + public Task save(Task task) { + + assignProject(task); + + assignUser(task); + + return taskRepository.save(task); + } + + private void assignUser(Task task) { + UUID userId = task.getUser().getId(); + User user = userService.findById(userId); + task.setUser(user); + } + + private void assignProject(Task task) { + UUID projectId = task.getProject().getId(); + Project project = projectService.findById(projectId); + task.setProject(project); + } + + public Task updateTask(UUID taskId, Task task) { + Task fetch = findById(taskId); + + + fetch.setTitle(task.getTitle()); + fetch.setDescription(task.getDescription()); + fetch.setStatus(task.getStatus()); + assignProject(task); + assignUser(task); + + return taskRepository.save(fetch); + } + + public Task patchTask(UUID taskId, Task task) { + Task fetch = findById(taskId); + + if (task.getTitle() != null) { + fetch.setTitle(task.getTitle()); + } + if (task.getDescription() != null) { + fetch.setDescription(task.getDescription()); + } + if (task.getStatus() != null) { + fetch.setStatus(task.getStatus()); + } + if (task.getProject() != null) { + assignProject(task); + } + if (task.getUser() != null) { + assignUser(task); + } + + return taskRepository.save(fetch); + } + + public void delete(UUID taskId) { + Task fetch = findById(taskId); + taskRepository.delete(fetch); + } + + + public Task updateTaskStatus(UUID taskId, TaskStatus status) { + Task fetch = findById(taskId); + patchTask(taskId,fetch).setStatus(status); + return null; + } +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/service/UserService.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/UserService.java new file mode 100644 index 0000000..8b2d6e6 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/UserService.java @@ -0,0 +1,62 @@ +package com.accenture.codingtest.springbootcodingtest.service; + +import com.accenture.codingtest.springbootcodingtest.entity.User; +import com.accenture.codingtest.springbootcodingtest.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Service +public class UserService { + + + private final UserRepository userRepository; + + @Autowired + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public User save(User user) { + userRepository.save(user); + return user; + } + + public User findById(UUID userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("User not found with userId: " + userId)); + return user; + } + + public List findAll() { + return userRepository.findAll(); + } + + public User updateUser(UUID userId, User user) { + User fetch = findById(userId); + fetch.setUsername(user.getUsername()); + fetch.setPassword(user.getPassword()); + return userRepository.save(fetch); + } + + public User patchUser(UUID userId, User user){ + User fetch = findById(userId); + if (user.getUsername() != null){ + fetch.setUsername(user.getUsername()); + } + if (user.getPassword() != null){ + fetch.setPassword(user.getPassword()); + } + + return userRepository.save(fetch); + } + + public void delete(UUID userId) { + User fetch = findById(userId); + userRepository.deleteById(userId); + } + +} From 5afe9063ef6f8d67c168e3a124a2dab01576a78f Mon Sep 17 00:00:00 2001 From: pirangicharan Date: Sat, 27 May 2023 21:48:55 +0530 Subject: [PATCH 08/10] pagination done --- .../SpringBootCodingTestApplication.java | 13 ++- .../controller/ProjectController.java | 17 ++-- .../controller/UserController.java | 6 +- .../springbootcodingtest/entity/Project.java | 1 - .../springbootcodingtest/entity/User.java | 17 ---- .../model/DataInitializer.java | 83 +++++++++++++++++++ .../service/ProjectService.java | 17 ++-- .../service/TaskService.java | 2 - .../service/UserService.java | 4 +- 9 files changed, 112 insertions(+), 48 deletions(-) create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/model/DataInitializer.java diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/SpringBootCodingTestApplication.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/SpringBootCodingTestApplication.java index d0085bd..ae1287b 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/SpringBootCodingTestApplication.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/SpringBootCodingTestApplication.java @@ -1,14 +1,19 @@ package com.accenture.codingtest.springbootcodingtest; +import com.accenture.codingtest.springbootcodingtest.entity.Project; +import com.accenture.codingtest.springbootcodingtest.service.ProjectService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication -public class SpringBootCodingTestApplication{ +public class SpringBootCodingTestApplication { - public static void main(String[] args) { - SpringApplication.run(SpringBootCodingTestApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(SpringBootCodingTestApplication.class, args); + + + } } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java index 21b7751..c5166bf 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java @@ -3,11 +3,14 @@ import com.accenture.codingtest.springbootcodingtest.entity.Project; import com.accenture.codingtest.springbootcodingtest.service.ProjectService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.List; import java.util.UUID; @RestController @@ -24,10 +27,13 @@ public ProjectController(ProjectService projectService) { @GetMapping - //@PreAuthorize("hasRole('ADMIN') or hasRole('PRODUCT_OWNER')") - public ResponseEntity> getAllProjects() { - List projects = projectService.findAll(); - return ResponseEntity.ok(projects); + public Page getProjects( + @RequestParam(defaultValue = "3") int page, + @RequestParam(defaultValue = "5") int size, + @RequestParam(defaultValue = "name") String sort + ) { + Pageable pageable = PageRequest.of(page, size, Sort.by(sort).descending()); + return projectService.getAllProjects(pageable); } @GetMapping("/{project_id}") @@ -37,7 +43,6 @@ public ResponseEntity getProjectById(@PathVariable("project_id") UUID p } @PostMapping - //@PreAuthorize("hasRole('PRODUCT_OWNER')") public ResponseEntity createProject(@RequestBody Project project) { Project savedProject = projectService.save(project); return ResponseEntity.status(HttpStatus.CREATED).body(savedProject); diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java index 01f5440..c213d7a 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/UserController.java @@ -12,8 +12,7 @@ import java.util.UUID; @RestController -@RequestMapping(value = "/api/v1/users") -//@PreAuthorize("hasRole('ADMIN')") +@RequestMapping("/api/v1/users") public class UserController { private final UserService userService; @@ -63,7 +62,4 @@ public ResponseEntity deleteUser(@PathVariable("user_id") UUID userId){ return ResponseEntity.noContent().build(); } - - - } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java index 20839da..654ed82 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java @@ -13,7 +13,6 @@ public class Project { @Column(nullable = false,unique = true) private String name; - public UUID getId() { return id; } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java index 04b72e8..eb99fdb 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java @@ -1,9 +1,6 @@ package com.accenture.codingtest.springbootcodingtest.entity; -import com.accenture.codingtest.springbootcodingtest.model.Role; - import javax.persistence.*; -import java.util.Set; import java.util.UUID; @Entity @@ -18,21 +15,7 @@ public class User { private String username; @Column(nullable = false) private String password; -/* - @ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER) - @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) - @Enumerated(EnumType.STRING) - private Set role; - - public Set getRole() { - return role; - } - - public void setRole(Set role) { - this.role = role; - } - */ public UUID getId() { return id; diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/model/DataInitializer.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/model/DataInitializer.java new file mode 100644 index 0000000..02221a4 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/model/DataInitializer.java @@ -0,0 +1,83 @@ +package com.accenture.codingtest.springbootcodingtest.model; + +import com.accenture.codingtest.springbootcodingtest.entity.Project; +import com.accenture.codingtest.springbootcodingtest.repository.ProjectRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class DataInitializer implements CommandLineRunner { + private final ProjectRepository projectRepository; + + @Autowired + public DataInitializer(ProjectRepository projectRepository) { + this.projectRepository = projectRepository; + } + + @Override + public void run(String... args) { + + Project project1 = new Project(); + project1.setName("project1"); + projectRepository.save(project1); + + Project project2 = new Project(); + project2.setName("project2"); + projectRepository.save(project2); + + Project project3 = new Project(); + project3.setName("project3"); + projectRepository.save(project3); + + Project project4 = new Project(); + project4.setName("project4"); + projectRepository.save(project4); + + Project project5 = new Project(); + project5.setName("project5"); + projectRepository.save(project5); + + Project project6 = new Project(); + project6.setName("project6"); + projectRepository.save(project6); + + Project project7 = new Project(); + project7.setName("project7"); + projectRepository.save(project7); + + Project project8 = new Project(); + project8.setName("project8"); + projectRepository.save(project8); + + Project project9 = new Project(); + project9.setName("project9"); + projectRepository.save(project9); + + Project project10 = new Project(); + project10.setName("project10"); + projectRepository.save(project10); + + Project project11 = new Project(); + project11.setName("project11"); + projectRepository.save(project11); + + Project project12 = new Project(); + project12.setName("project12"); + projectRepository.save(project12); + + Project project13 = new Project(); + project13.setName("project13"); + projectRepository.save(project13); + + Project project14 = new Project(); + project14.setName("project14"); + projectRepository.save(project14); + + Project project15 = new Project(); + project15.setName("project15"); + projectRepository.save(project15); + + + } +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/service/ProjectService.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/ProjectService.java index 3857ac7..98d7925 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/service/ProjectService.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/ProjectService.java @@ -1,16 +1,15 @@ package com.accenture.codingtest.springbootcodingtest.service; import com.accenture.codingtest.springbootcodingtest.entity.Project; -import com.accenture.codingtest.springbootcodingtest.entity.User; import com.accenture.codingtest.springbootcodingtest.repository.ProjectRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.Optional; import java.util.UUID; -@Service +@Service("projectService") public class ProjectService { private final ProjectRepository projectRepository; @@ -22,18 +21,16 @@ public ProjectService(ProjectRepository projectRepository) { public Project findById(UUID projectId) { - Project project = projectRepository.findById(projectId) + return projectRepository.findById(projectId) .orElseThrow(() -> new RuntimeException("Project not found with Id: " + projectId)); - return project; } - public List findAll() { - return projectRepository.findAll(); + public Page getAllProjects(Pageable pageable) { + return projectRepository.findAll(pageable); } public Project save(Project project) { - Project saved = projectRepository.save(project); - return saved; + return projectRepository.save(project); } public Project updateProject(UUID projectId, Project project) { diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/service/TaskService.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/TaskService.java index d6ba56c..0c9757b 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/service/TaskService.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/TaskService.java @@ -4,9 +4,7 @@ import com.accenture.codingtest.springbootcodingtest.entity.Task; import com.accenture.codingtest.springbootcodingtest.entity.User; import com.accenture.codingtest.springbootcodingtest.model.TaskStatus; -import com.accenture.codingtest.springbootcodingtest.repository.ProjectRepository; import com.accenture.codingtest.springbootcodingtest.repository.TaskRepository; -import com.accenture.codingtest.springbootcodingtest.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/service/UserService.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/UserService.java index 8b2d6e6..eb1d6a6 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/service/UserService.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/UserService.java @@ -6,7 +6,6 @@ import org.springframework.stereotype.Service; import java.util.List; -import java.util.Optional; import java.util.UUID; @Service @@ -26,9 +25,8 @@ public User save(User user) { } public User findById(UUID userId) { - User user = userRepository.findById(userId) + return userRepository.findById(userId) .orElseThrow(() -> new RuntimeException("User not found with userId: " + userId)); - return user; } public List findAll() { From f028f72bcd7b4b2946c8a33c91c697d21eac7be9 Mon Sep 17 00:00:00 2001 From: pirangicharan Date: Sun, 28 May 2023 11:11:55 +0530 Subject: [PATCH 09/10] pagination updated --- .../SpringBootCodingTestApplication.java | 4 ++ .../controller/ProjectController.java | 58 ++++++++++--------- .../springbootcodingtest/entity/Project.java | 10 +++- .../service/ProjectService.java | 7 ++- 4 files changed, 49 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/SpringBootCodingTestApplication.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/SpringBootCodingTestApplication.java index ae1287b..e79cc63 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/SpringBootCodingTestApplication.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/SpringBootCodingTestApplication.java @@ -5,6 +5,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; @SpringBootApplication @@ -14,6 +17,7 @@ public static void main(String[] args) { SpringApplication.run(SpringBootCodingTestApplication.class, args); + } } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java index c5166bf..e74b2c2 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/controller/ProjectController.java @@ -11,6 +11,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; import java.util.UUID; @RestController @@ -25,42 +26,43 @@ public ProjectController(ProjectService projectService) { } - @GetMapping - public Page getProjects( - @RequestParam(defaultValue = "3") int page, + public ResponseEntity> getAllProjects( + @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "5") int size, - @RequestParam(defaultValue = "name") String sort + @RequestParam(defaultValue = "name") String sortBy ) { - Pageable pageable = PageRequest.of(page, size, Sort.by(sort).descending()); - return projectService.getAllProjects(pageable); + Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy)); + Page projects = projectService.getAllProjects(pageable); + return ResponseEntity.ok(projects); } - @GetMapping("/{project_id}") - public ResponseEntity getProjectById(@PathVariable("project_id") UUID projectId) { - Project project = projectService.findById(projectId); - return ResponseEntity.ok(project); - } - @PostMapping - public ResponseEntity createProject(@RequestBody Project project) { - Project savedProject = projectService.save(project); - return ResponseEntity.status(HttpStatus.CREATED).body(savedProject); - } + @GetMapping("/{project_id}") + public ResponseEntity getProjectById (@PathVariable("project_id") UUID projectId){ + Project project = projectService.findById(projectId); + return ResponseEntity.ok(project); + } - @PutMapping("/{project_id}") - public ResponseEntity updateProjectById(@PathVariable("project_id") UUID projectId, - @RequestBody Project project) { - Project updated = projectService.updateProject(projectId,project); + @PostMapping + public ResponseEntity createProject (@RequestBody Project project){ + Project savedProject = projectService.save(project); + return ResponseEntity.status(HttpStatus.CREATED).body(savedProject); + } - return ResponseEntity.ok(updated); - } + @PutMapping("/{project_id}") + public ResponseEntity updateProjectById (@PathVariable("project_id") UUID projectId, + @RequestBody Project project){ + Project updated = projectService.updateProject(projectId, project); - @DeleteMapping("/{project_id}") - public ResponseEntity deleteProject(@PathVariable("project_id") UUID projectId) { - projectService.delete(projectId); - return ResponseEntity.noContent().build(); - } + return ResponseEntity.ok(updated); + } + + @DeleteMapping("/{project_id}") + public ResponseEntity deleteProject (@PathVariable("project_id") UUID projectId){ + projectService.delete(projectId); + return ResponseEntity.noContent().build(); + } -} + } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java index 654ed82..0875f85 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/Project.java @@ -10,7 +10,7 @@ public class Project { @Id @GeneratedValue(strategy = GenerationType.AUTO) private UUID id; - @Column(nullable = false,unique = true) + @Column(name = "name",nullable = false,unique = true) private String name; public UUID getId() { @@ -28,4 +28,12 @@ public String getName() { public void setName(String name) { this.name = name; } + + @Override + public String toString() { + return "Project { " + + "id= " + id + + " , name='" + name + + "' }"; + } } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/service/ProjectService.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/ProjectService.java index 98d7925..9ffd250 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/service/ProjectService.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/service/ProjectService.java @@ -5,8 +5,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.List; import java.util.UUID; @Service("projectService") @@ -29,6 +32,7 @@ public Page getAllProjects(Pageable pageable) { return projectRepository.findAll(pageable); } + public Project save(Project project) { return projectRepository.save(project); } @@ -39,10 +43,11 @@ public Project updateProject(UUID projectId, Project project) { return projectRepository.save(fetch); } - public void delete(UUID projectId){ + public void delete(UUID projectId) { Project fetch = findById(projectId); projectRepository.delete(fetch); } + } From 8f50cf2a861e16d53c5faf0bc69bb03ca0a5066d Mon Sep 17 00:00:00 2001 From: pirangicharan Date: Sun, 28 May 2023 19:04:57 +0530 Subject: [PATCH 10/10] Implement features --- pom.xml | 18 ++--- .../config/SecurityConfig.java | 72 +++++++++++++++++++ .../config/UserDetailsServiceImpl.java | 44 ++++++++++++ .../ProjectInitializer.java} | 6 +- .../dataInitializer/UserInitializer.java | 46 ++++++++++++ .../springbootcodingtest/entity/User.java | 16 +++++ .../springbootcodingtest/model/Role.java | 22 +++++- .../repository/UserRepository.java | 2 +- src/main/resources/application.properties | 2 +- 9 files changed, 212 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/config/SecurityConfig.java create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/config/UserDetailsServiceImpl.java rename src/main/java/com/accenture/codingtest/springbootcodingtest/{model/DataInitializer.java => dataInitializer/ProjectInitializer.java} (92%) create mode 100644 src/main/java/com/accenture/codingtest/springbootcodingtest/dataInitializer/UserInitializer.java diff --git a/pom.xml b/pom.xml index b98dbea..bfb1ae6 100644 --- a/pom.xml +++ b/pom.xml @@ -30,9 +30,17 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-devtools + runtime + true + - - + + org.springframework.boot + spring-boot-starter-security + com.h2database h2 @@ -41,12 +49,6 @@ org.springframework.boot spring-boot-starter-data-jpa - - org.springframework.boot - spring-boot-devtools - runtime - true - diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/config/SecurityConfig.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/config/SecurityConfig.java new file mode 100644 index 0000000..a1679eb --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/config/SecurityConfig.java @@ -0,0 +1,72 @@ +package com.accenture.codingtest.springbootcodingtest.config; + +import com.accenture.codingtest.springbootcodingtest.model.Role; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + private final UserDetailsService userDetailsService; + + public SecurityConfig(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests() + .antMatchers(HttpMethod.GET, "/api/v1/users").hasRole(Role.ADMIN.name()) + .antMatchers(HttpMethod.GET, "/api/v1/users/{user_id}").hasRole(Role.ADMIN.name()) + .antMatchers(HttpMethod.POST, "/api/v1/users").hasRole(Role.ADMIN.name()) + .antMatchers(HttpMethod.PUT, "/api/v1/users/{user_id}").hasRole(Role.ADMIN.name()) + .antMatchers(HttpMethod.PATCH, "/api/v1/users/{user_id}").hasRole(Role.ADMIN.name()) + .antMatchers(HttpMethod.DELETE, "/api/v1/users/{user_id}").hasRole(Role.ADMIN.name()) + + .antMatchers(HttpMethod.GET, "/api/v1/projects").hasAnyRole(Role.ADMIN.name(),Role.PROJECT_OWNER.name()) + .antMatchers(HttpMethod.GET, "/api/v1/projects/{project_id}").hasAnyRole(Role.ADMIN.name(),Role.PROJECT_OWNER.name()) + .antMatchers(HttpMethod.POST, "/api/v1/projects").hasRole(Role.PROJECT_OWNER.name()) + .antMatchers(HttpMethod.PUT, "/api/v1/projects/{project_id}").hasAnyRole(Role.ADMIN.name(),Role.PROJECT_OWNER.name()) + .antMatchers(HttpMethod.DELETE, "/api/v1/projects/{project_id}").hasRole(Role.ADMIN.name()) + + .antMatchers(HttpMethod.GET, "/api/v1/tasks").hasAnyRole(Role.ADMIN.name(),Role.PROJECT_OWNER.name()) + .antMatchers(HttpMethod.GET, "/api/v1/tasks/{task_id}").hasAnyRole(Role.ADMIN.name(),Role.PROJECT_OWNER.name()) + .antMatchers(HttpMethod.POST, "/api/v1/tasks").hasRole(Role.PROJECT_OWNER.name()) + .antMatchers(HttpMethod.PUT, "/api/v1/tasks/{task_id}").hasRole(Role.PROJECT_OWNER.name()) + .antMatchers(HttpMethod.PATCH, "/api/v1/tasks/{task_id}").hasRole(Role.PROJECT_OWNER.name()) + .antMatchers(HttpMethod.DELETE, "/api/v1/tasks/{task_id}").hasAnyRole(Role.ADMIN.name(),Role.PROJECT_OWNER.name()) + .antMatchers(HttpMethod.PATCH, "/api/v1/tasks/{task_id}/status").hasRole(Role.TEAM_MEMBER.name()) + + .anyRequest().authenticated() + .and() + .httpBasic(); + } + + + @Override + public void configure(WebSecurity web) throws Exception { + web + .ignoring() + .antMatchers("/h2-console/**"); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/config/UserDetailsServiceImpl.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/config/UserDetailsServiceImpl.java new file mode 100644 index 0000000..4a2f595 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/config/UserDetailsServiceImpl.java @@ -0,0 +1,44 @@ +package com.accenture.codingtest.springbootcodingtest.config; + +import com.accenture.codingtest.springbootcodingtest.entity.User; +import com.accenture.codingtest.springbootcodingtest.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + private final UserRepository userRepository; + + @Autowired + public UserDetailsServiceImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + + + Set authorities = user.getRoles().stream() + .map(role -> new SimpleGrantedAuthority(role.getAuthority())) + .collect(Collectors.toSet()); + + return new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPassword(), + authorities + ); + } + +} + + diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/model/DataInitializer.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/dataInitializer/ProjectInitializer.java similarity index 92% rename from src/main/java/com/accenture/codingtest/springbootcodingtest/model/DataInitializer.java rename to src/main/java/com/accenture/codingtest/springbootcodingtest/dataInitializer/ProjectInitializer.java index 02221a4..46cc19d 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/model/DataInitializer.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/dataInitializer/ProjectInitializer.java @@ -1,4 +1,4 @@ -package com.accenture.codingtest.springbootcodingtest.model; +package com.accenture.codingtest.springbootcodingtest.dataInitializer; import com.accenture.codingtest.springbootcodingtest.entity.Project; import com.accenture.codingtest.springbootcodingtest.repository.ProjectRepository; @@ -7,11 +7,11 @@ import org.springframework.stereotype.Component; @Component -public class DataInitializer implements CommandLineRunner { +public class ProjectInitializer implements CommandLineRunner { private final ProjectRepository projectRepository; @Autowired - public DataInitializer(ProjectRepository projectRepository) { + public ProjectInitializer(ProjectRepository projectRepository) { this.projectRepository = projectRepository; } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/dataInitializer/UserInitializer.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/dataInitializer/UserInitializer.java new file mode 100644 index 0000000..3650441 --- /dev/null +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/dataInitializer/UserInitializer.java @@ -0,0 +1,46 @@ +package com.accenture.codingtest.springbootcodingtest.dataInitializer; + +import com.accenture.codingtest.springbootcodingtest.entity.User; +import com.accenture.codingtest.springbootcodingtest.model.Role; +import com.accenture.codingtest.springbootcodingtest.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +@Component +public class UserInitializer implements CommandLineRunner { + + private final UserRepository userRepository; + + @Autowired + public UserInitializer(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public void run(String... args) throws Exception { + User user1 = new User(); + user1.setUsername("projectOwner"); + user1.setPassword(passwordEncoder().encode("1234")); + user1.getRoles().add(Role.PROJECT_OWNER); + userRepository.save(user1); + + User user2 = new User(); + user2.setUsername("admin"); + user2.setPassword(passwordEncoder().encode("1234")); + user2.getRoles().add(Role.ADMIN); + userRepository.save(user2); + + User user3 = new User(); + user3.setUsername("teamMember"); + user3.setPassword(passwordEncoder().encode("1234")); + user3.getRoles().add(Role.TEAM_MEMBER); + userRepository.save(user3); + } + + private PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java index eb99fdb..34c81fd 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/entity/User.java @@ -1,6 +1,10 @@ package com.accenture.codingtest.springbootcodingtest.entity; +import com.accenture.codingtest.springbootcodingtest.model.Role; + import javax.persistence.*; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; @Entity @@ -16,6 +20,18 @@ public class User { @Column(nullable = false) private String password; + @ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER) + @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) + @Enumerated(EnumType.STRING) + private Set roles = new HashSet<>(); + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } public UUID getId() { return id; diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/model/Role.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/model/Role.java index a1a1ff8..8bc2f47 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/model/Role.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/model/Role.java @@ -1,7 +1,23 @@ package com.accenture.codingtest.springbootcodingtest.model; public enum Role { - ADMIN, - PRODUCT_OWNER, - TEAM_MEMBER + PROJECT_OWNER("Project Owner", "ROLE_PROJECT_OWNER"), + ADMIN("Admin", "ROLE_ADMIN"), + TEAM_MEMBER("Team Member", "ROLE_TEAM_MEMBER"); + + private final String roleName; + private final String authority; + + Role(String roleName, String authority) { + this.roleName = roleName; + this.authority = authority; + } + + public String getRoleName() { + return roleName; + } + + public String getAuthority() { + return authority; + } } diff --git a/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/UserRepository.java b/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/UserRepository.java index 8c9f80f..14c3767 100644 --- a/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/UserRepository.java +++ b/src/main/java/com/accenture/codingtest/springbootcodingtest/repository/UserRepository.java @@ -7,6 +7,6 @@ import java.util.UUID; public interface UserRepository extends JpaRepository { - Optional findByUsername(String userName); + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7304338..f6f4510 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,6 +3,6 @@ spring.h2.console.enabled=true spring.datasource.url=jdbc:h2:mem:dcbapp spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa -spring.datasource.password=password +spring.datasource.password= spring.jpa.database-platform=org.hibernate.dialect.H2Dialect