From 58d32cf07231c49e2c9216d0c3aec58343d159a1 Mon Sep 17 00:00:00 2001 From: baohoang Date: Mon, 11 Jul 2022 02:50:46 +0700 Subject: [PATCH 1/4] First commit --- .gitignore | 36 ++++- README.md | 50 +++--- .../manabie/todotask/body/AddTaskRequest.java | 19 +++ .../manabie/todotask/body/BaseResponse.java | 21 +++ .../todotask/controller/TaskController.java | 32 ++++ .../com/manabie/todotask/entity/Task.java | 34 ++++ .../todotask/entity/UserDailyLimit.java | 20 +++ .../exception/ReachLimitException.java | 16 ++ .../exception/UserNotFoundException.java | 14 ++ .../todotask/repository/TaskRepository.java | 15 ++ .../todotask/repository/UserRepository.java | 11 ++ .../manabie/todotask/service/TaskService.java | 8 + .../todotask/service/TaskServiceImpl.java | 44 ++++++ todotask/src/main/resources/data.sql | 1 + todotask/src/main/resources/schema.sql | 15 ++ .../TaskControllerIntegrationTest.java | 148 ++++++++++++++++++ .../repository/TaskRepositoryUnitTest.java | 92 +++++++++++ .../repository/UserRepositoryUnitTest.java | 29 ++++ .../TaskServiceImplIntegrationTest.java | 104 ++++++++++++ 19 files changed, 679 insertions(+), 30 deletions(-) create mode 100644 todotask/src/main/java/com/manabie/todotask/body/AddTaskRequest.java create mode 100644 todotask/src/main/java/com/manabie/todotask/body/BaseResponse.java create mode 100644 todotask/src/main/java/com/manabie/todotask/controller/TaskController.java create mode 100644 todotask/src/main/java/com/manabie/todotask/entity/Task.java create mode 100644 todotask/src/main/java/com/manabie/todotask/entity/UserDailyLimit.java create mode 100644 todotask/src/main/java/com/manabie/todotask/exception/ReachLimitException.java create mode 100644 todotask/src/main/java/com/manabie/todotask/exception/UserNotFoundException.java create mode 100644 todotask/src/main/java/com/manabie/todotask/repository/TaskRepository.java create mode 100644 todotask/src/main/java/com/manabie/todotask/repository/UserRepository.java create mode 100644 todotask/src/main/java/com/manabie/todotask/service/TaskService.java create mode 100644 todotask/src/main/java/com/manabie/todotask/service/TaskServiceImpl.java create mode 100644 todotask/src/main/resources/data.sql create mode 100644 todotask/src/main/resources/schema.sql create mode 100644 todotask/src/test/java/com/manabie/todotask/controller/TaskControllerIntegrationTest.java create mode 100644 todotask/src/test/java/com/manabie/todotask/repository/TaskRepositoryUnitTest.java create mode 100644 todotask/src/test/java/com/manabie/todotask/repository/UserRepositoryUnitTest.java create mode 100644 todotask/src/test/java/com/manabie/todotask/service/TaskServiceImplIntegrationTest.java diff --git a/.gitignore b/.gitignore index a512c8b38..a152a990b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,38 @@ out/ -.idea \ No newline at end of file +.idea +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + diff --git a/README.md b/README.md index 8df9d4d3a..e56578a6f 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,22 @@ -### Requirements -- Implement one single API which accepts a todo task and records it - - There is a maximum **limit of N tasks per user** that can be added **per day**. - - Different users can have **different** maximum daily limit. -- Write integration (functional) tests -- Write unit tests -- Choose a suitable architecture to make your code simple, organizable, and maintainable -- Write a concise README - - How to run your code locally? - - A sample “curl” command to call your API - - How to run your unit tests locally? - - What do you love about your solution? - - What else do you want us to know about however you do not have enough time to complete? - -### Notes - -- We're using Golang at Manabie. **However**, we encourage you to use the programming language that you are most comfortable with because we want you to **shine** with all your skills and knowledge. - -### How to submit your solution? - -- Fork this repo and show us your development progress via a PR - -### Interesting facts about Manabie - -- Monthly there are about 2 million lines of code changes (inserted/updated/deleted) committed into our GitHub repositories. To avoid **regression bugs**, we write different kinds of **automated tests** (unit/integration (functionality)/end2end) as parts of the definition of done of our assigned tasks. -- We nurture the cultural values: **knowledge sharing** and **good communication**, therefore good written documents and readable, organizable, and maintainable code are in our blood when we build any features to grow our products. -- We have **collaborative** culture at Manabie. Feel free to ask trieu@manabie.com any questions. We are very happy to answer all of them. - -Thank you for spending time to read and attempt our take-home assessment. We are looking forward to your submission. +## Run the application +First, you need to install maven and java with version >= 1.8. +Open terminal and `cd` to folder `todotask`. After that, run this command +`mvn spring-boot:run` to initialize the application. +## Verify running application +After initializing application successfully, you can test the api by sending this curl request. +```cookie +curl --location --request POST 'http://localhost:8080/task/add' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "user_id": 1, + "task_name": "test", + "task_description":"test", + "target_date":"2022-07-08T00:00:00Z" +}' +``` +## Run unit and integration tests +For unit testing and integration testing, run this command ```mvn clean test``` + +## Some +This project uses H2, an in-memory database, for the purpose of making the application locally runnable without an external database like Postrges or Mysql. The schema and initial data defined in two files `schema.sql` and `data.sql`, are stored in folder ```src/main/java/resources```. The schema file is also used for testing. \ No newline at end of file diff --git a/todotask/src/main/java/com/manabie/todotask/body/AddTaskRequest.java b/todotask/src/main/java/com/manabie/todotask/body/AddTaskRequest.java new file mode 100644 index 000000000..caf081340 --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/body/AddTaskRequest.java @@ -0,0 +1,19 @@ +package com.manabie.todotask.body; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.time.ZonedDateTime; +import java.util.Date; + +@Data +public class AddTaskRequest { + @JsonProperty("target_date") + private ZonedDateTime targetDate; + @JsonProperty("user_id") + private Integer userId; + @JsonProperty("task_name") + private String taskName; + @JsonProperty("task_description") + private String taskDescription; +} diff --git a/todotask/src/main/java/com/manabie/todotask/body/BaseResponse.java b/todotask/src/main/java/com/manabie/todotask/body/BaseResponse.java new file mode 100644 index 000000000..e97e31468 --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/body/BaseResponse.java @@ -0,0 +1,21 @@ +package com.manabie.todotask.body; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +public class BaseResponse { + private int code; + private String message; + private Object data; + + public BaseResponse(int code, String message){ + this.code = code; + this.message = message; + } + + public BaseResponse withData(Object data){ + this.data = data; + return this; + } +} diff --git a/todotask/src/main/java/com/manabie/todotask/controller/TaskController.java b/todotask/src/main/java/com/manabie/todotask/controller/TaskController.java new file mode 100644 index 000000000..e0b287aa9 --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/controller/TaskController.java @@ -0,0 +1,32 @@ +package com.manabie.todotask.controller; + +import com.manabie.todotask.body.AddTaskRequest; +import com.manabie.todotask.body.BaseResponse; +import com.manabie.todotask.entity.Task; +import com.manabie.todotask.exception.ReachLimitException; +import com.manabie.todotask.exception.UserNotFoundException; +import com.manabie.todotask.service.TaskService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.client.HttpClientErrorException; + +@Controller +@RequestMapping("/task") +public class TaskController { + @Autowired + TaskService taskService; + + @PutMapping("/add") + public @ResponseBody ResponseEntity addTask(@RequestBody AddTaskRequest request){ + try { + Task task = taskService.addTask(request); + return ResponseEntity.ok(new BaseResponse(0, "success").withData(task)); + } catch (ReachLimitException | UserNotFoundException e){ + return ResponseEntity.badRequest().body(new BaseResponse(1, e.getMessage())); + } catch (Exception e){ + return ResponseEntity.internalServerError().body(new BaseResponse(2, "Internal Server Error")); + } + } +} diff --git a/todotask/src/main/java/com/manabie/todotask/entity/Task.java b/todotask/src/main/java/com/manabie/todotask/entity/Task.java new file mode 100644 index 000000000..5eb892c7d --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/entity/Task.java @@ -0,0 +1,34 @@ +package com.manabie.todotask.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import javax.persistence.*; +import java.time.ZonedDateTime; +import java.util.Date; + +@Data +@Entity +@Table(name = "`tasks`") +public class Task { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "`id`") + private Integer id; + + @JsonProperty("user_id") + @Column(name = "`user_id`") + private Integer userId; + + @JsonProperty("description") + @Column(name = "`description`") + private String description; + + @JsonProperty("name") + @Column(name = "`name`") + private String name; + + @JsonProperty("target_date") + @Column(name = "`target_date`") + private ZonedDateTime targetDate; +} diff --git a/todotask/src/main/java/com/manabie/todotask/entity/UserDailyLimit.java b/todotask/src/main/java/com/manabie/todotask/entity/UserDailyLimit.java new file mode 100644 index 000000000..f0ef54f9f --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/entity/UserDailyLimit.java @@ -0,0 +1,20 @@ +package com.manabie.todotask.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import javax.persistence.*; + +@Entity +@Data +@Table(name = "`user_daily_limits`") +public class UserDailyLimit { + @Id + @JsonProperty("user_id") + @Column(name="`user_id`") + private Integer userId; + + @JsonProperty("daily_task_limit") + @Column(name="`daily_task_limit`") + private Integer dailyTaskLimit; +} diff --git a/todotask/src/main/java/com/manabie/todotask/exception/ReachLimitException.java b/todotask/src/main/java/com/manabie/todotask/exception/ReachLimitException.java new file mode 100644 index 000000000..bae562310 --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/exception/ReachLimitException.java @@ -0,0 +1,16 @@ +package com.manabie.todotask.exception; + +import lombok.Data; + +import java.time.ZonedDateTime; + +@Data +public class ReachLimitException extends RuntimeException{ + protected Integer userId; + protected ZonedDateTime dateTime; + public ReachLimitException(Integer userId, ZonedDateTime date){ + super("User " + userId + " has reached max task limit at date " + date); + this.userId = userId; + this.dateTime = date; + } +} diff --git a/todotask/src/main/java/com/manabie/todotask/exception/UserNotFoundException.java b/todotask/src/main/java/com/manabie/todotask/exception/UserNotFoundException.java new file mode 100644 index 000000000..e96bd6347 --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/exception/UserNotFoundException.java @@ -0,0 +1,14 @@ +package com.manabie.todotask.exception; + +import lombok.Data; + +import java.util.Date; + +@Data +public class UserNotFoundException extends RuntimeException{ + private Integer userId; + public UserNotFoundException(Integer userId){ + super("User with id " + userId + " does not exist"); + this.userId = userId; + } +} diff --git a/todotask/src/main/java/com/manabie/todotask/repository/TaskRepository.java b/todotask/src/main/java/com/manabie/todotask/repository/TaskRepository.java new file mode 100644 index 000000000..aea9332bf --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/repository/TaskRepository.java @@ -0,0 +1,15 @@ +package com.manabie.todotask.repository; + +import com.manabie.todotask.entity.Task; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import java.time.ZonedDateTime; +import java.util.Date; + +@Repository +public interface TaskRepository extends CrudRepository { + @Query("SELECT COUNT (t) FROM Task t where t.userId = :userId and t.targetDate >= :startDate and t.targetDate < :endDate") + int countTaskByUserIdAndTargetDate(Integer userId, ZonedDateTime startDate, ZonedDateTime endDate); +} diff --git a/todotask/src/main/java/com/manabie/todotask/repository/UserRepository.java b/todotask/src/main/java/com/manabie/todotask/repository/UserRepository.java new file mode 100644 index 000000000..52c89cf9e --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/repository/UserRepository.java @@ -0,0 +1,11 @@ +package com.manabie.todotask.repository; + +import com.manabie.todotask.entity.UserDailyLimit; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import javax.persistence.Entity; + +@Repository +public interface UserRepository extends CrudRepository { +} diff --git a/todotask/src/main/java/com/manabie/todotask/service/TaskService.java b/todotask/src/main/java/com/manabie/todotask/service/TaskService.java new file mode 100644 index 000000000..9aec1658c --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/service/TaskService.java @@ -0,0 +1,8 @@ +package com.manabie.todotask.service; + +import com.manabie.todotask.body.AddTaskRequest; +import com.manabie.todotask.entity.Task; + +public interface TaskService { + Task addTask(AddTaskRequest request); +} diff --git a/todotask/src/main/java/com/manabie/todotask/service/TaskServiceImpl.java b/todotask/src/main/java/com/manabie/todotask/service/TaskServiceImpl.java new file mode 100644 index 000000000..4c79b4c20 --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/service/TaskServiceImpl.java @@ -0,0 +1,44 @@ +package com.manabie.todotask.service; + +import com.manabie.todotask.body.AddTaskRequest; +import com.manabie.todotask.entity.Task; +import com.manabie.todotask.exception.ReachLimitException; +import com.manabie.todotask.exception.UserNotFoundException; +import com.manabie.todotask.repository.TaskRepository; +import com.manabie.todotask.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; + +import javax.management.BadAttributeValueExpException; +import java.time.LocalDate; +import java.time.ZonedDateTime; + +@Service +public class TaskServiceImpl implements TaskService{ + @Autowired + TaskRepository taskRepository; + @Autowired + UserRepository userRepository; + + public Task addTask(AddTaskRequest request){ + return userRepository.findById(request.getUserId()).map(userDailyLimit -> { + int taskCount = taskRepository.countTaskByUserIdAndTargetDate(request.getUserId(), request.getTargetDate(), request.getTargetDate().plusDays(1)); + if (taskCount < userDailyLimit.getDailyTaskLimit()) { + return taskRepository.save(buildFromRequest(request)); + } else { + throw new ReachLimitException(request.getUserId(), request.getTargetDate()); + } + }).orElseThrow(() -> new UserNotFoundException(request.getUserId())); + } + + private Task buildFromRequest(AddTaskRequest request){ + Task task = new Task(); + task.setUserId(request.getUserId()); + task.setDescription(request.getTaskDescription()); + task.setName(request.getTaskName()); + task.setTargetDate(request.getTargetDate()); + return task; + } +} diff --git a/todotask/src/main/resources/data.sql b/todotask/src/main/resources/data.sql new file mode 100644 index 000000000..1545591e0 --- /dev/null +++ b/todotask/src/main/resources/data.sql @@ -0,0 +1 @@ +insert into "user_daily_limits" ("user_id", "daily_task_limit") values (1, 5) \ No newline at end of file diff --git a/todotask/src/main/resources/schema.sql b/todotask/src/main/resources/schema.sql new file mode 100644 index 000000000..2f90a1813 --- /dev/null +++ b/todotask/src/main/resources/schema.sql @@ -0,0 +1,15 @@ +DROP TABLE IF EXISTS tasks; +DROP TABLE IF EXISTS user_daily_limits; + +CREATE TABLE IF NOT EXISTS "tasks" ( + "id" int AUTO_INCREMENT PRIMARY KEY, + "user_id" int, + "name" VARCHAR , + "description" VARCHAR, + "target_date" TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS "user_daily_limits" ( + "user_id" int PRIMARY KEY, + "daily_task_limit" int +); diff --git a/todotask/src/test/java/com/manabie/todotask/controller/TaskControllerIntegrationTest.java b/todotask/src/test/java/com/manabie/todotask/controller/TaskControllerIntegrationTest.java new file mode 100644 index 000000000..8af169100 --- /dev/null +++ b/todotask/src/test/java/com/manabie/todotask/controller/TaskControllerIntegrationTest.java @@ -0,0 +1,148 @@ +package com.manabie.todotask.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.manabie.todotask.body.AddTaskRequest; +import com.manabie.todotask.entity.Task; +import com.manabie.todotask.entity.UserDailyLimit; +import com.manabie.todotask.repository.TaskRepository; +import com.manabie.todotask.repository.UserRepository; +import com.manabie.todotask.service.TaskService; +import javafx.application.Application; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.time.ZonedDateTime; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@AutoConfigureMockMvc +@EnableAutoConfiguration(exclude= SecurityAutoConfiguration.class) +@AutoConfigureTestDatabase +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +class TaskControllerIntegrationTest { + @Autowired + MockMvc mockMvc; + + @Autowired + TaskService taskService; + @Autowired + UserRepository userRepository; + @Autowired + TaskRepository taskRepository; + + ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); + + @BeforeEach + public void setup(){ + taskRepository.deleteAll(); + userRepository.deleteAll(); + UserDailyLimit userDailyLimit = new UserDailyLimit(); + userDailyLimit.setUserId(1); + userDailyLimit.setDailyTaskLimit(5); + userRepository.save(userDailyLimit); + } + + @Test + void addTaskAndReturnSuccess() throws Exception { + int userId = 1; + ZonedDateTime targetDate = ZonedDateTime.parse("2022-07-08T00:00:00Z"); + AddTaskRequest request = new AddTaskRequest(); + request.setUserId(userId); + request.setTaskName("test"); + request.setTaskDescription("test"); + request.setTargetDate(targetDate); + + RequestBuilder requestBuilder = MockMvcRequestBuilders + .put("/task/add") + .content(mapper.writeValueAsBytes(request)) + .contentType(MediaType.APPLICATION_JSON); + + mockMvc.perform(requestBuilder) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code", is(0))) + .andExpect(jsonPath("$.data.id", is(1))); + } + + @Test + void addTaskWithUserNotExist() throws Exception { + int userId = 2; + ZonedDateTime targetDate = ZonedDateTime.parse("2022-07-08T00:00:00Z"); + AddTaskRequest request = new AddTaskRequest(); + request.setUserId(userId); + request.setTaskName("test"); + request.setTaskDescription("test"); + request.setTargetDate(targetDate); + + RequestBuilder requestBuilder = MockMvcRequestBuilders + .put("/task/add") + .content(mapper.writeValueAsBytes(request)) + .contentType(MediaType.APPLICATION_JSON); + + mockMvc.perform(requestBuilder) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code", is(1))) + .andExpect(jsonPath("$.message", is("User with id " + userId + " does not exist"))); + } + + @Test + void addTaskWithReachLimitTask() throws Exception { + int userId = 1; + Optional userDailyLimitOpt = userRepository.findById(userId); + assert (userDailyLimitOpt.isPresent()); + UserDailyLimit userDailyLimit = userDailyLimitOpt.get(); + + ZonedDateTime targetDate = ZonedDateTime.parse("2022-07-08T00:00:00Z"); + AddTaskRequest request = new AddTaskRequest(); + request.setUserId(userId); + request.setTaskName("test"); + request.setTaskDescription("test"); + request.setTargetDate(targetDate); + + RequestBuilder requestBuilder = MockMvcRequestBuilders + .put("/task/add") + .content(mapper.writeValueAsBytes(request)) + .contentType(MediaType.APPLICATION_JSON); + + //add until reach limit + for (int i = 0; i < userDailyLimit.getDailyTaskLimit(); i ++) { + mockMvc.perform(requestBuilder) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code", is(0))) + .andExpect(jsonPath("$.data.user_id", is(userId))); + } + //add when limit is reached + mockMvc.perform(requestBuilder) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code", is(1))); + } +} \ No newline at end of file diff --git a/todotask/src/test/java/com/manabie/todotask/repository/TaskRepositoryUnitTest.java b/todotask/src/test/java/com/manabie/todotask/repository/TaskRepositoryUnitTest.java new file mode 100644 index 000000000..a8f414cd1 --- /dev/null +++ b/todotask/src/test/java/com/manabie/todotask/repository/TaskRepositoryUnitTest.java @@ -0,0 +1,92 @@ +package com.manabie.todotask.repository; + +import com.manabie.todotask.entity.Task; +import com.manabie.todotask.entity.UserDailyLimit; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +import java.time.ZonedDateTime; +import java.util.Optional; + +@SpringBootTest +@AutoConfigureTestDatabase +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +class TaskRepositoryUnitTest { + + @Autowired + TaskRepository taskRepository; + @Test + public void addAndFind(){ + int userId = 1; + ZonedDateTime targetDate = ZonedDateTime.parse("2022-07-08T00:00:00Z"); + + Task task = new Task(); + task.setName("test"); + task.setUserId(userId); + task.setDescription("test"); + task.setTargetDate(targetDate); + Task t = taskRepository.save(task); + System.out.println(t); + assert(t.getId().equals(1)); + + Optional indbOpt = taskRepository.findById(t.getId()); + assert (indbOpt.isPresent()); + Task indb = indbOpt.get(); + + assert (indbOpt.get().getUserId().equals(userId)); + assert (indbOpt.get().getName().equals("test")); + assert (indbOpt.get().getDescription().equals("test")); + assert (indbOpt.get().getTargetDate().toInstant().equals(targetDate.toInstant())); + + } + + @Test + void countTaskByUserIdAndTargetDate() { + int userId = 1; + int userId2 = 2; + ZonedDateTime targetDate = ZonedDateTime.parse("2022-07-08T00:00:00Z"); + ZonedDateTime targetDate2 = ZonedDateTime.parse("2022-07-09T00:00:00Z"); + + Task task = new Task(); + task.setName("test"); + task.setUserId(userId); + task.setDescription("test"); + task.setTargetDate(targetDate); + Task t = taskRepository.save(task); + System.out.println(t); + assert(t.getId().equals(1)); + + Task task2 = new Task(); + task2.setName("test"); + task2.setUserId(userId); + task2.setDescription("test"); + task2.setTargetDate(targetDate); + Task t2 = taskRepository.save(task2); + System.out.println(t2); + assert(t2.getId().equals(2)); + + Task task3 = new Task(); + task3.setName("test"); + task3.setUserId(userId2); + task3.setDescription("test"); + task3.setTargetDate(targetDate); + Task t3 = taskRepository.save(task3); + System.out.println(t3); + assert(t3.getId().equals(3)); + + Task task4 = new Task(); + task4.setName("test"); + task4.setUserId(userId); + task4.setDescription("test"); + task4.setTargetDate(targetDate2); + Task t4 = taskRepository.save(task4); + System.out.println(t4); + assert(t4.getId().equals(4)); + + int count = taskRepository.countTaskByUserIdAndTargetDate(userId, targetDate, targetDate.plusDays(1)); + assert (count == 2); + } +} \ No newline at end of file diff --git a/todotask/src/test/java/com/manabie/todotask/repository/UserRepositoryUnitTest.java b/todotask/src/test/java/com/manabie/todotask/repository/UserRepositoryUnitTest.java new file mode 100644 index 000000000..7091686d0 --- /dev/null +++ b/todotask/src/test/java/com/manabie/todotask/repository/UserRepositoryUnitTest.java @@ -0,0 +1,29 @@ +package com.manabie.todotask.repository; + +import com.manabie.todotask.entity.UserDailyLimit; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +import java.util.Optional; + +@SpringBootTest +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +class UserRepositoryUnitTest { + @Autowired + UserRepository userRepository; + + @Test + public void addAndFind(){ + UserDailyLimit userDailyLimit = new UserDailyLimit(); + userDailyLimit.setUserId(1); + userDailyLimit.setDailyTaskLimit(5); + UserDailyLimit added = userRepository.save(userDailyLimit); + + Optional indbOpt = userRepository.findById(userDailyLimit.getUserId()); + assert (indbOpt.isPresent()); + assert (indbOpt.get().getUserId().equals(userDailyLimit.getUserId())); + assert (indbOpt.get().getDailyTaskLimit().equals(userDailyLimit.getDailyTaskLimit())); + } +} \ No newline at end of file diff --git a/todotask/src/test/java/com/manabie/todotask/service/TaskServiceImplIntegrationTest.java b/todotask/src/test/java/com/manabie/todotask/service/TaskServiceImplIntegrationTest.java new file mode 100644 index 000000000..192007b6f --- /dev/null +++ b/todotask/src/test/java/com/manabie/todotask/service/TaskServiceImplIntegrationTest.java @@ -0,0 +1,104 @@ +package com.manabie.todotask.service; + +import com.manabie.todotask.body.AddTaskRequest; +import com.manabie.todotask.entity.Task; +import com.manabie.todotask.entity.UserDailyLimit; +import com.manabie.todotask.exception.ReachLimitException; +import com.manabie.todotask.exception.UserNotFoundException; +import com.manabie.todotask.repository.TaskRepository; +import com.manabie.todotask.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +import java.time.ZonedDateTime; +import java.util.Optional; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@SpringBootTest +@AutoConfigureTestDatabase +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +class TaskServiceImplIntegrationTest { + @Autowired + TaskRepository taskRepository; + + @Autowired + UserRepository userRepository; + + @Autowired + TaskServiceImpl taskService; + + @BeforeEach + public void setup(){ + taskRepository.deleteAll(); + userRepository.deleteAll(); + UserDailyLimit userDailyLimit = new UserDailyLimit(); + userDailyLimit.setUserId(1); + userDailyLimit.setDailyTaskLimit(5); + userRepository.save(userDailyLimit); + } + + @Test + void addTaskAndSuccess() { + int userId = 1; + ZonedDateTime targetDate = ZonedDateTime.parse("2022-07-08T00:00:00Z"); + + AddTaskRequest request = new AddTaskRequest(); + request.setUserId(userId); + request.setTaskName("test"); + request.setTaskDescription("test"); + request.setTargetDate(targetDate); + + Task task = taskService.addTask(request); + + assert(task.getId().equals(1)); + assert(task.getUserId().equals(userId)); + } + + @Test + void addTaskWithUserNotExist() { + int userId = 2; + ZonedDateTime targetDate = ZonedDateTime.parse("2022-07-08T00:00:00Z"); + + AddTaskRequest request = new AddTaskRequest(); + request.setUserId(userId); + request.setTaskName("test"); + request.setTaskDescription("test"); + request.setTargetDate(targetDate); + + UserNotFoundException e = assertThrows(UserNotFoundException.class, () -> taskService.addTask(request)); + + assert(e.getUserId().equals(userId)); + } + + @Test + void addTaskWithUserReachLimitTask() { + int userId = 1; + Optional userDailyLimitOpt = userRepository.findById(userId); + assert (userDailyLimitOpt.isPresent()); + UserDailyLimit userDailyLimit = userDailyLimitOpt.get(); + + ZonedDateTime targetDate = ZonedDateTime.parse("2022-07-08T00:00:00Z"); + + AddTaskRequest request = new AddTaskRequest(); + request.setUserId(userId); + request.setTaskName("test"); + request.setTaskDescription("test"); + request.setTargetDate(targetDate); + + ReachLimitException e = assertThrows(ReachLimitException.class, () -> { + for (int i = 0; i <= userDailyLimit.getDailyTaskLimit(); i ++) { + taskService.addTask(request); + } + }); + assert(e.getDateTime().equals(request.getTargetDate())); + assert(e.getUserId().equals(userId)); + } +} \ No newline at end of file From 18c065549cb8e5bebe46be5ad08e078635e56c94 Mon Sep 17 00:00:00 2001 From: baohoang Date: Mon, 11 Jul 2022 03:24:59 +0700 Subject: [PATCH 2/4] Refactor --- README.md | 26 +++++++++++++------ .../manabie/todotask/body/AddTaskRequest.java | 6 ++++- .../manabie/todotask/body/BaseResponse.java | 9 +++---- .../todotask/controller/TaskController.java | 21 +++++++++------ .../handler/InvalidRequestHandler.java | 17 ++++++++++++ .../todotask/service/TaskServiceImpl.java | 8 +++--- 6 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 todotask/src/main/java/com/manabie/todotask/handler/InvalidRequestHandler.java diff --git a/README.md b/README.md index e56578a6f..0d839e66f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ +### Run the application + +First, you need to install `maven` and `java` with `version >= 1.8`. +Open terminal and `cd` to folder `todotask`. After that, run this command `mvn spring-boot:run` to initialize the +application. + +### Verify running application -## Run the application -First, you need to install maven and java with version >= 1.8. -Open terminal and `cd` to folder `todotask`. After that, run this command -`mvn spring-boot:run` to initialize the application. -## Verify running application After initializing application successfully, you can test the api by sending this curl request. + ```cookie curl --location --request POST 'http://localhost:8080/task/add' \ --header 'Content-Type: application/json' \ @@ -15,8 +18,15 @@ curl --location --request POST 'http://localhost:8080/task/add' \ "target_date":"2022-07-08T00:00:00Z" }' ``` -## Run unit and integration tests + +### Run unit and integration tests + For unit testing and integration testing, run this command ```mvn clean test``` -## Some -This project uses H2, an in-memory database, for the purpose of making the application locally runnable without an external database like Postrges or Mysql. The schema and initial data defined in two files `schema.sql` and `data.sql`, are stored in folder ```src/main/java/resources```. The schema file is also used for testing. \ No newline at end of file +### Some important notes + +1. Database and server configuartion are configure in file ```src/main/resources/application.properties```. Server will + start at default port `8080` +2. This project uses H2, an in-memory database, for the purpose of making the application locally runnable without an + external database like Postrges or Mysql. The schema and initial data defined in two files `schema.sql` + and `data.sql`, are stored in folder ```src/main/java/resources```. The schema file is also used for testing. \ No newline at end of file diff --git a/todotask/src/main/java/com/manabie/todotask/body/AddTaskRequest.java b/todotask/src/main/java/com/manabie/todotask/body/AddTaskRequest.java index caf081340..80d43c446 100644 --- a/todotask/src/main/java/com/manabie/todotask/body/AddTaskRequest.java +++ b/todotask/src/main/java/com/manabie/todotask/body/AddTaskRequest.java @@ -3,15 +3,19 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; +import javax.validation.constraints.NotNull; import java.time.ZonedDateTime; -import java.util.Date; @Data public class AddTaskRequest { @JsonProperty("target_date") + @NotNull(message = "Target date must not be null") private ZonedDateTime targetDate; + @JsonProperty("user_id") + @NotNull(message = "User id must not be null") private Integer userId; + @JsonProperty("task_name") private String taskName; @JsonProperty("task_description") diff --git a/todotask/src/main/java/com/manabie/todotask/body/BaseResponse.java b/todotask/src/main/java/com/manabie/todotask/body/BaseResponse.java index e97e31468..e2f1bbb64 100644 --- a/todotask/src/main/java/com/manabie/todotask/body/BaseResponse.java +++ b/todotask/src/main/java/com/manabie/todotask/body/BaseResponse.java @@ -1,20 +1,19 @@ package com.manabie.todotask.body; -import lombok.AllArgsConstructor; import lombok.Data; @Data public class BaseResponse { - private int code; + private int status; private String message; private Object data; - public BaseResponse(int code, String message){ - this.code = code; + public BaseResponse(int status, String message) { + this.status = status; this.message = message; } - public BaseResponse withData(Object data){ + public BaseResponse withData(Object data) { this.data = data; return this; } diff --git a/todotask/src/main/java/com/manabie/todotask/controller/TaskController.java b/todotask/src/main/java/com/manabie/todotask/controller/TaskController.java index e0b287aa9..d5393b028 100644 --- a/todotask/src/main/java/com/manabie/todotask/controller/TaskController.java +++ b/todotask/src/main/java/com/manabie/todotask/controller/TaskController.java @@ -7,10 +7,15 @@ import com.manabie.todotask.exception.UserNotFoundException; import com.manabie.todotask.service.TaskService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.validation.Valid; @Controller @RequestMapping("/task") @@ -19,14 +24,14 @@ public class TaskController { TaskService taskService; @PutMapping("/add") - public @ResponseBody ResponseEntity addTask(@RequestBody AddTaskRequest request){ + public @ResponseBody ResponseEntity addTask(@RequestBody @Valid AddTaskRequest request) { try { Task task = taskService.addTask(request); - return ResponseEntity.ok(new BaseResponse(0, "success").withData(task)); - } catch (ReachLimitException | UserNotFoundException e){ - return ResponseEntity.badRequest().body(new BaseResponse(1, e.getMessage())); - } catch (Exception e){ - return ResponseEntity.internalServerError().body(new BaseResponse(2, "Internal Server Error")); + return ResponseEntity.ok(new BaseResponse(HttpStatus.OK.value(), "success").withData(task)); + } catch (ReachLimitException | UserNotFoundException e) { + return ResponseEntity.badRequest().body(new BaseResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage())); + } catch (Exception e) { + return ResponseEntity.internalServerError().body(new BaseResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error")); } } } diff --git a/todotask/src/main/java/com/manabie/todotask/handler/InvalidRequestHandler.java b/todotask/src/main/java/com/manabie/todotask/handler/InvalidRequestHandler.java new file mode 100644 index 000000000..b096c07f9 --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/handler/InvalidRequestHandler.java @@ -0,0 +1,17 @@ +package com.manabie.todotask.handler; + +import com.manabie.todotask.body.BaseResponse; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class InvalidRequestHandler { + @ExceptionHandler(BindException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public BaseResponse handleBindException(BindException e) { + return new BaseResponse(HttpStatus.BAD_REQUEST.value(), e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); + } +} diff --git a/todotask/src/main/java/com/manabie/todotask/service/TaskServiceImpl.java b/todotask/src/main/java/com/manabie/todotask/service/TaskServiceImpl.java index 4c79b4c20..361d4fe41 100644 --- a/todotask/src/main/java/com/manabie/todotask/service/TaskServiceImpl.java +++ b/todotask/src/main/java/com/manabie/todotask/service/TaskServiceImpl.java @@ -7,12 +7,8 @@ import com.manabie.todotask.repository.TaskRepository; import com.manabie.todotask.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import org.springframework.web.client.HttpClientErrorException; -import javax.management.BadAttributeValueExpException; -import java.time.LocalDate; import java.time.ZonedDateTime; @Service @@ -23,8 +19,10 @@ public class TaskServiceImpl implements TaskService{ UserRepository userRepository; public Task addTask(AddTaskRequest request){ + ZonedDateTime startTime = request.getTargetDate().toLocalDate().atStartOfDay().atZone(request.getTargetDate().getZone()); + ZonedDateTime endTime = request.getTargetDate().toLocalDate().atStartOfDay().plusDays(1).atZone(request.getTargetDate().getZone()); return userRepository.findById(request.getUserId()).map(userDailyLimit -> { - int taskCount = taskRepository.countTaskByUserIdAndTargetDate(request.getUserId(), request.getTargetDate(), request.getTargetDate().plusDays(1)); + int taskCount = taskRepository.countTaskByUserIdAndTargetDate(request.getUserId(), startTime, endTime); if (taskCount < userDailyLimit.getDailyTaskLimit()) { return taskRepository.save(buildFromRequest(request)); } else { From 60bdfa22bd2f76ac9eeaf18c98ef715b795b3908 Mon Sep 17 00:00:00 2001 From: baohoang Date: Mon, 11 Jul 2022 03:32:22 +0700 Subject: [PATCH 3/4] Refactor --- .../TaskControllerIntegrationTest.java | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/todotask/src/test/java/com/manabie/todotask/controller/TaskControllerIntegrationTest.java b/todotask/src/test/java/com/manabie/todotask/controller/TaskControllerIntegrationTest.java index 8af169100..740e49421 100644 --- a/todotask/src/test/java/com/manabie/todotask/controller/TaskControllerIntegrationTest.java +++ b/todotask/src/test/java/com/manabie/todotask/controller/TaskControllerIntegrationTest.java @@ -2,13 +2,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.manabie.todotask.body.AddTaskRequest; -import com.manabie.todotask.entity.Task; import com.manabie.todotask.entity.UserDailyLimit; import com.manabie.todotask.repository.TaskRepository; import com.manabie.todotask.repository.UserRepository; import com.manabie.todotask.service.TaskService; -import javafx.application.Application; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -16,30 +13,21 @@ import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import java.time.ZonedDateTime; +import java.util.Optional; + import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.hasSize; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.time.ZonedDateTime; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; - @SpringBootTest @AutoConfigureMockMvc @EnableAutoConfiguration(exclude= SecurityAutoConfiguration.class) @@ -86,7 +74,7 @@ void addTaskAndReturnSuccess() throws Exception { mockMvc.perform(requestBuilder) .andDo(print()) .andExpect(status().isOk()) - .andExpect(jsonPath("$.code", is(0))) + .andExpect(jsonPath("$.status", is(200))) .andExpect(jsonPath("$.data.id", is(1))); } @@ -108,7 +96,7 @@ void addTaskWithUserNotExist() throws Exception { mockMvc.perform(requestBuilder) .andDo(print()) .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.code", is(1))) + .andExpect(jsonPath("$.status", is(400))) .andExpect(jsonPath("$.message", is("User with id " + userId + " does not exist"))); } @@ -136,13 +124,13 @@ void addTaskWithReachLimitTask() throws Exception { mockMvc.perform(requestBuilder) .andDo(print()) .andExpect(status().isOk()) - .andExpect(jsonPath("$.code", is(0))) + .andExpect(jsonPath("$.status", is(200))) .andExpect(jsonPath("$.data.user_id", is(userId))); } //add when limit is reached mockMvc.perform(requestBuilder) .andDo(print()) .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.code", is(1))); + .andExpect(jsonPath("$.status", is(400))); } } \ No newline at end of file From f84865bddbb3481ae01a2ee84280016b81eef95c Mon Sep 17 00:00:00 2001 From: baohoang Date: Mon, 11 Jul 2022 09:47:24 +0700 Subject: [PATCH 4/4] Add missing file, and add some detail in README.md --- README.md | 26 +- todotask/mvnw | 316 ++++++++++++++++++ todotask/mvnw.cmd | 188 +++++++++++ todotask/pom.xml | 77 +++++ .../manabie/todotask/TodotaskApplication.java | 13 + .../src/main/resources/application.properties | 8 + 6 files changed, 620 insertions(+), 8 deletions(-) create mode 100755 todotask/mvnw create mode 100644 todotask/mvnw.cmd create mode 100644 todotask/pom.xml create mode 100644 todotask/src/main/java/com/manabie/todotask/TodotaskApplication.java create mode 100644 todotask/src/main/resources/application.properties diff --git a/README.md b/README.md index 0d839e66f..e7f84c065 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ ### Run the application First, you need to install `maven` and `java` with `version >= 1.8`. -Open terminal and `cd` to folder `todotask`. After that, run this command `mvn spring-boot:run` to initialize the +Open terminal and `cd` to folder `todotask`. After that, run this command `mvn spring-boot:run` to run the application. ### Verify running application -After initializing application successfully, you can test the api by sending this curl request. +After running application successfully, you can test the api by sending this `CURL` request. ```cookie -curl --location --request POST 'http://localhost:8080/task/add' \ +curl --location --request PUT 'http://localhost:8080/task/add' \ --header 'Content-Type: application/json' \ --data-raw '{ "user_id": 1, @@ -23,10 +23,20 @@ curl --location --request POST 'http://localhost:8080/task/add' \ For unit testing and integration testing, run this command ```mvn clean test``` -### Some important notes +### Some highlights about the project -1. Database and server configuartion are configure in file ```src/main/resources/application.properties```. Server will - start at default port `8080` -2. This project uses H2, an in-memory database, for the purpose of making the application locally runnable without an +1. This application contains three layer: controller, service and repository. Each layer communicates with each other + via interface not their concrete methods. Therefore, each layer is easily replaceable and extendable. +2. `ZonedTimeDate` is used instead of `Date`. This new class helps us maintain daily limit logic for different users in + different timezones. +3. `Spring Validation` and `Global Exception Handling` are used to passively verify request format and throws predefined + response if request format is invalid. For example, if field `user_id` or `target_date` in `AddTaskRequest` is null, + application will return a bad request response immediately. +4. This project uses H2, an in-memory database, for the purpose of making the application locally runnable without an external database like Postrges or Mysql. The schema and initial data defined in two files `schema.sql` - and `data.sql`, are stored in folder ```src/main/java/resources```. The schema file is also used for testing. \ No newline at end of file + and `data.sql`, are stored in folder ```src/main/java/resources```. The schema file is also used for testing. + +### Other notes + +1. Database and server configuartion are configure in file ```src/main/resources/application.properties```. Server will + start at the default port `8080` diff --git a/todotask/mvnw b/todotask/mvnw new file mode 100755 index 000000000..8a8fb2282 --- /dev/null +++ b/todotask/mvnw @@ -0,0 +1,316 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/todotask/mvnw.cmd b/todotask/mvnw.cmd new file mode 100644 index 000000000..1d8ab018e --- /dev/null +++ b/todotask/mvnw.cmd @@ -0,0 +1,188 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/todotask/pom.xml b/todotask/pom.xml new file mode 100644 index 000000000..5a0aaf30e --- /dev/null +++ b/todotask/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.1 + + + com.manabie + todotask + 0.0.1-SNAPSHOT + todotask + To do task prj + + 1.8 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + com.h2database + h2 + runtime + + + org.openjfx + javafx-controls + 17.0.1 + + + org.springframework.boot + spring-boot-starter-validation + 2.7.1 + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/todotask/src/main/java/com/manabie/todotask/TodotaskApplication.java b/todotask/src/main/java/com/manabie/todotask/TodotaskApplication.java new file mode 100644 index 000000000..ff88b716a --- /dev/null +++ b/todotask/src/main/java/com/manabie/todotask/TodotaskApplication.java @@ -0,0 +1,13 @@ +package com.manabie.todotask; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TodotaskApplication { + + public static void main(String[] args) { + SpringApplication.run(TodotaskApplication.class, args); + } + +} diff --git a/todotask/src/main/resources/application.properties b/todotask/src/main/resources/application.properties new file mode 100644 index 000000000..77ef15cff --- /dev/null +++ b/todotask/src/main/resources/application.properties @@ -0,0 +1,8 @@ +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.h2.console.enabled=true +spring.jpa.hibernate.ddl-auto=none +spring.sql.init.mode=always \ No newline at end of file