diff --git a/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/controller/ToDoController.java b/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/controller/ToDoController.java index 6b20956..1fa5d45 100644 --- a/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/controller/ToDoController.java +++ b/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/controller/ToDoController.java @@ -7,10 +7,7 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.*; import jp.ac.kobe_u.cs.itspecialist.todoapp.dto.LoginForm; import jp.ac.kobe_u.cs.itspecialist.todoapp.dto.ToDoForm; @@ -92,6 +89,16 @@ public class ToDoController { return "redirect:/" + mid + "/todos"; } + /** + * ToDoの更新.期限を追加,削除する.その後,ユーザページへリダイレクトする. + */ + @PutMapping("/{mid}/todos/{seq}/due") + String updateDueDate(@PathVariable String mid, @PathVariable Long seq, + @Validated @ModelAttribute(name = "ToDoForm") ToDoForm form, Model model) { + tService.updateDueDate(mid, seq, form.getDueDate()); + return "redirect:/" + mid + "/todos"; + } + /** * ToDoの完了.完了処理後,ユーザページへリダイレクト */ diff --git a/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/dto/ToDoForm.java b/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/dto/ToDoForm.java index b2f6fc4..7e3a5bd 100644 --- a/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/dto/ToDoForm.java +++ b/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/dto/ToDoForm.java @@ -1,5 +1,9 @@ package jp.ac.kobe_u.cs.itspecialist.todoapp.dto; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Date; import javax.validation.constraints.NotBlank; @@ -7,6 +11,7 @@ import javax.validation.constraints.Size; import jp.ac.kobe_u.cs.itspecialist.todoapp.entity.ToDo; import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; /** * ToDoの入力フォーム @@ -16,12 +21,29 @@ public class ToDoForm { @NotBlank @Size(min=1, max=64) String title; //ToDo題目 + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + LocalDateTime due; // 期限 public ToDo toEntity() { ToDo t = new ToDo(); t.setTitle(title); t.setCreatedAt(new Date()); t.setDone(false); + t.setDueAt(getDueDate()); return t; } + + public Date getDueDate() { + if (due == null) { + return null; + } + return toDate(due); + } + + private Date toDate(LocalDateTime ldt) { + ZoneId id = ZoneId.systemDefault(); + ZonedDateTime zdt = ZonedDateTime.of(ldt, id); + Instant instant = zdt.toInstant(); + return Date.from(instant); + } } diff --git a/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/entity/ToDo.java b/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/entity/ToDo.java index c0f901f..130158e 100644 --- a/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/entity/ToDo.java +++ b/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/entity/ToDo.java @@ -1,5 +1,7 @@ package jp.ac.kobe_u.cs.itspecialist.todoapp.entity; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.Date; import javax.persistence.Entity; @@ -9,6 +11,7 @@ import javax.persistence.Id; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import jp.ac.kobe_u.cs.itspecialist.todoapp.dto.ToDoForm; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -31,4 +34,26 @@ public class ToDo { Date createdAt; //作成日時 @Temporal(TemporalType.TIMESTAMP) Date doneAt; //完了日時 + @Temporal(TemporalType.TIMESTAMP) + Date dueAt; //期限 + + public boolean isValidDueDate() { + if (dueAt == null) { // dueが設定されていなければ無視する. + return true; + } + if (done) { // 完了していれば,更新不可. + return false; + } + // 期限は作成日よりも後でなければならない. + return dueAt.after(createdAt); + } + + private static final DateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + public String getDueString() { + if(dueAt == null) { + return ""; + } + return FORMATTER.format(dueAt); + } } diff --git a/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/service/ToDoService.java b/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/service/ToDoService.java index 57a6371..05627bd 100644 --- a/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/service/ToDoService.java +++ b/src/main/java/jp/ac/kobe_u/cs/itspecialist/todoapp/service/ToDoService.java @@ -2,6 +2,7 @@ package jp.ac.kobe_u.cs.itspecialist.todoapp.service; import java.util.Date; import java.util.List; +import java.util.Objects; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -26,6 +27,10 @@ public class ToDoService { public ToDo createToDo(String mid, ToDoForm form) { mService.getMember(mid); //実在メンバーか確認 ToDo todo = form.toEntity(); + if(!todo.isValidDueDate()) { + throw new ToDoAppException(ToDoAppException.INVALID_TODO_OPERATION, + todo.getDueAt() + ": should be after created at"); + } todo.setMid(mid); return tRepo.save(todo); } @@ -76,6 +81,25 @@ public class ToDoService { return tRepo.findByDone(true); } + /** + * 〆切を更新する. + * @param mid + * @param seq + * @param due + */ + public ToDo updateDueDate(String mid, Long seq, Date due) { + ToDo todo = getToDo(seq); + if (!Objects.equals(mid, todo.getMid())) { + throw new ToDoAppException(ToDoAppException.INVALID_TODO_OPERATION, mid + + ": Cannot done other's todo of " + todo.getMid()); + } + if(due != null && due.before(todo.getCreatedAt())) { + throw new ToDoAppException(ToDoAppException.INVALID_TODO_OPERATION, + due + ": should be after created at."); + } + todo.setDueAt(due); + return tRepo.save(todo); + } /** * ToDoを完了する @@ -87,8 +111,8 @@ public class ToDoService { ToDo todo = getToDo(seq); //Doneの認可を確認する.他人のToDoを閉めたらダメ. if (!mid.equals(todo.getMid())) { - throw new ToDoAppException(ToDoAppException.INVALID_TODO_OPERATION, mid - + ": Cannot done other's todo of " + todo.getMid()); + throw new ToDoAppException(ToDoAppException.INVALID_TODO_OPERATION, mid + + ": Cannot done other's todo of " + todo.getMid()); } todo.setDone(true); todo.setDoneAt(new Date()); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e07c446..c3ae487 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,3 +9,8 @@ spring.datasource.password=todotodo # Spring-JPA: DBのテーブルを自動作成してくれる機能 # create: 新規作成, update: なければ新規作成, create-drop: 新規作成し終了時に削除 spring.jpa.hibernate.ddl-auto=update + +# HTML から PUT, DELETE リクエスを投げるために必要. +# ただし,MultipartFileがリクエストに含まれると時間がかかるらしい. +# https://qiita.com/kazuhiro1982/items/b8b9965fddf9c5507517 +spring.mvc.hiddenmethod.filter.enabled=true \ No newline at end of file diff --git a/src/main/resources/templates/alllist.html b/src/main/resources/templates/alllist.html index 8580517..0a89b2c 100644 --- a/src/main/resources/templates/alllist.html +++ b/src/main/resources/templates/alllist.html @@ -19,12 +19,14 @@ <th>タイトル</th> <th>作成者</th> <th>作成日時</th> + <th>期日</th> </tr> <tr th:each="todo: ${todos}"> <td>[[${todo.seq}]]</td> <td>[[${todo.title}]]</td> <td>[[${todo.mid}]]</td> <td>[[${todo.createdAt}]]</td> + <td>[[${todo.dueAt}]]</td> </tr> </table> @@ -34,12 +36,14 @@ <th>#</th> <th>タイトル</th> <th>作成者</th> + <th>期日</th> <th>完了日時</th> </tr> <tr th:each="done: ${dones}"> <td>[[${done.seq}]]</td> <td>[[${done.title}]]</td> <td>[[${done.mid}]]</td> + <td>[[${todo.dueAt}]]</td> <td>[[${done.doneAt}]]</td> </tr> </table> diff --git a/src/main/resources/templates/list.html b/src/main/resources/templates/list.html index a2939b9..c464443 100644 --- a/src/main/resources/templates/list.html +++ b/src/main/resources/templates/list.html @@ -18,13 +18,20 @@ <th>#</th> <th>タイトル</th> <th>作成日時</th> + <th>期日</th> <th>コマンド</th> </tr> <tr th:each="todo: ${todos}"> <td>[[${todo.seq}]]</td> <td>[[${todo.title}]]</td> + <td>[[${todo.dueAt}]]</td> <td>[[${todo.createdAt}]]</td> <td> + <form th:action="@{/{mid}/todos/{seq}/due(mid=${member.mid},seq=${todo.seq})}" th:method="put" th:object="${ToDoForm}"> + <input type="hidden" name="title" th:value="${todo.title}" /> + <input type="datetime-local" name="due" th:value="${todo.getDueString()}" /> + <input type="submit" value="期日を設定" /> + </form> <a th:href="@{/{mid}/todos/{seq}/done(mid=${member.mid},seq=${todo.seq})}">完了</a> </td> </tr> @@ -32,9 +39,10 @@ <td> * </td> - <td colspan="3"> + <td colspan="4"> <form role="form" th:action="@{/{mid}/todos(mid=${member.mid})}" th:object="${ToDoForm}" method="post"> <input type="text" required th:field="*{title}" /> + <input type="datetime-local" th:field="*{due}" /> <input type="submit" value="新規作成" /> <div th:if="${#fields.hasErrors('title')}" th:errors="*{title}" style="color: red"></div> </form> @@ -48,12 +56,14 @@ <th>#</th> <th>タイトル</th> <th>作成日時</th> + <th>期日</th> <th>完了日時</th> </tr> <tr th:each="done: ${dones}"> <td>[[${done.seq}]]</td> <td>[[${done.title}]]</td> <td>[[${done.createdAt}]]</td> + <td>[[${done.dueAt}]]</td> <td>[[${done.doneAt}]]</td> </tr> </table>