From 52e4c84e367e1637879932d7573ba161f2e22a5c Mon Sep 17 00:00:00 2001
From: Hoai Viet Nguyen <viet.nguyen@th-koeln.de>
Date: Fri, 13 Dec 2024 12:42:35 +0100
Subject: [PATCH] add inputvalidaiton an errors hints

---
 build.gradle                                  |  1 +
 .../todolist/controllers/TasksController.kt   | 30 ++++++++++++++-----
 .../controllers/TasksRestController.kt        | 11 ++++---
 .../todolist/controllers/UsersController.kt   | 13 ++++++--
 .../controllers/UsersRestController.kt        | 11 ++++---
 .../de/thk/gm/gdw/todolist/dtos/TaskDto.kt    |  9 ++++++
 .../de/thk/gm/gdw/todolist/dtos/UserDto.kt    | 18 +++++++++++
 .../de/thk/gm/gdw/todolist/models/User.kt     |  3 ++
 .../resources/templates/tasks/showTask.ftlh   |  6 ++++
 .../resources/templates/tasks/showTasks.ftlh  |  3 ++
 .../resources/templates/users/showUsers.ftlh  | 11 ++++++-
 11 files changed, 97 insertions(+), 19 deletions(-)
 create mode 100644 src/main/kotlin/de/thk/gm/gdw/todolist/dtos/TaskDto.kt
 create mode 100644 src/main/kotlin/de/thk/gm/gdw/todolist/dtos/UserDto.kt

diff --git a/build.gradle b/build.gradle
index ae87069..2f9b71e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -31,6 +31,7 @@ dependencies {
     testImplementation 'org.springframework.boot:spring-boot-starter-test'
     testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
     testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+    implementation 'org.springframework.boot:spring-boot-starter-validation'
     implementation 'org.json:json:20231013'
 }
 
diff --git a/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/TasksController.kt b/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/TasksController.kt
index 19f8309..309e473 100644
--- a/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/TasksController.kt
+++ b/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/TasksController.kt
@@ -1,12 +1,16 @@
 package de.thk.gm.gdw.todolist.controllers
 
+import de.thk.gm.gdw.todolist.dtos.TaskDto
 import de.thk.gm.gdw.todolist.services.TasksService
 import de.thk.gm.gdw.todolist.services.UsersService
+import jakarta.validation.Valid
 import org.springframework.http.HttpStatus
 import org.springframework.http.MediaType
 import org.springframework.stereotype.Controller
 import org.springframework.ui.Model
+import org.springframework.validation.BindingResult
 import org.springframework.web.bind.annotation.*
+import org.springframework.web.servlet.mvc.support.RedirectAttributes
 import java.util.*
 
 @Controller
@@ -22,8 +26,13 @@ class TasksController (private val tasksRestController: TasksRestController, pri
     }
 
     @PostMapping("/tasks")
-    fun saveTask(name: String, @PathVariable userId: UUID): String {
-        tasksRestController.saveTask(name, userId)
+    fun saveTask(@Valid taskDto: TaskDto, bindingResult: BindingResult, redirectAttributes: RedirectAttributes, @PathVariable userId: UUID): String {
+        if (bindingResult.hasErrors()) {
+            redirectAttributes.addFlashAttribute("errors", bindingResult)
+        } else {
+            tasksRestController.saveTask(taskDto, userId)
+        }
+
         return "redirect:/users/$userId/tasks"
     }
 
@@ -35,12 +44,17 @@ class TasksController (private val tasksRestController: TasksRestController, pri
     }
 
     @PutMapping("/tasks/{id}")
-    fun updateTask(@PathVariable userId: UUID, @PathVariable id: UUID, name: String, open: Boolean): String {
-        var task = tasksRestController.getTaskById(id, userId)
-        task.name = name
-        task.open = open
-        tasksService.save(task)
-        return "redirect:/users/$userId/tasks/${task.id}"
+    fun updateTask(@PathVariable userId: UUID, @PathVariable id: UUID,@Valid taskDto: TaskDto,bindingResult: BindingResult, redirectAttributes: RedirectAttributes): String {
+        if(bindingResult.hasErrors()) {
+            redirectAttributes.addFlashAttribute("errors", bindingResult)
+        } else {
+            var task = tasksRestController.getTaskById(id, userId)
+            task.name = taskDto.name
+            task.open = taskDto.open
+            tasksService.save(task)
+        }
+
+        return "redirect:/users/$userId/tasks/${id}"
     }
 
     @DeleteMapping("/tasks/{id}")
diff --git a/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/TasksRestController.kt b/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/TasksRestController.kt
index 027a5ef..7ac541e 100644
--- a/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/TasksRestController.kt
+++ b/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/TasksRestController.kt
@@ -1,8 +1,10 @@
 package de.thk.gm.gdw.todolist.controllers
 
+import de.thk.gm.gdw.todolist.dtos.TaskDto
 import de.thk.gm.gdw.todolist.models.Task
 import de.thk.gm.gdw.todolist.services.TasksService
 import de.thk.gm.gdw.todolist.services.UsersService
+import jakarta.validation.Valid
 import org.springframework.http.HttpStatus
 import org.springframework.http.MediaType
 import org.springframework.web.bind.annotation.*
@@ -15,11 +17,11 @@ class TasksRestController (private val tasksService: TasksService, private val u
 
     @PostMapping("/tasks")
     @ResponseStatus(HttpStatus.CREATED)
-    fun saveTask(name : String, @PathVariable userId: UUID) {
+    fun saveTask(@Valid @RequestBody taskDto: TaskDto, @PathVariable userId: UUID) {
         val user  = usersService.getUserById(userId)
         if(user != null) {
             var task = Task()
-            task.name = name
+            task.name = taskDto.name
             task.user = user
             tasksService.save(task)
         } else {
@@ -59,12 +61,13 @@ class TasksRestController (private val tasksService: TasksService, private val u
 
     @PutMapping("/tasks/{id}")
     @ResponseStatus(HttpStatus.NO_CONTENT)
-    fun updateTask(@PathVariable("id") id : UUID, name : String, @PathVariable userId: UUID) {
+    fun updateTask(@PathVariable("id") id : UUID, @Valid @RequestBody taskDto: TaskDto, @PathVariable userId: UUID) {
         val user  = usersService.getUserById(userId)
         if(user != null) {
             var task = tasksService.getByIdAndUser(id, user)
             if(task != null) {
-                task.name = name
+                task.name = taskDto.name
+                task.open = taskDto.open
                 tasksService.save(task)
             } else {
                 throw ResponseStatusException(HttpStatus.NOT_FOUND)
diff --git a/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/UsersController.kt b/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/UsersController.kt
index 160859f..de178b6 100644
--- a/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/UsersController.kt
+++ b/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/UsersController.kt
@@ -1,11 +1,15 @@
 package de.thk.gm.gdw.todolist.controllers
 
+import de.thk.gm.gdw.todolist.dtos.UserDto
+import jakarta.validation.Valid
 import org.springframework.http.MediaType
 import org.springframework.stereotype.Controller
 import org.springframework.ui.Model
+import org.springframework.validation.BindingResult
 import org.springframework.web.bind.annotation.GetMapping
 import org.springframework.web.bind.annotation.PostMapping
 import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.servlet.mvc.support.RedirectAttributes
 
 @Controller
 @RequestMapping(produces = [MediaType.TEXT_HTML_VALUE])
@@ -18,8 +22,13 @@ class UsersController (private val usersRestController: UsersRestController) {
     }
 
     @PostMapping("/users")
-    fun saveUsers(email: String): String {
-        usersRestController.saveUser(email)
+    fun saveUsers(@Valid userDto: UserDto, bindingResult: BindingResult, redirectAttributes: RedirectAttributes): String {
+
+        if (bindingResult.hasErrors()) {
+            redirectAttributes.addFlashAttribute("errors", bindingResult)
+        } else {
+           usersRestController.saveUser(userDto)
+        }
         return "redirect:/"
     }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/UsersRestController.kt b/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/UsersRestController.kt
index cd3a56b..f41d566 100644
--- a/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/UsersRestController.kt
+++ b/src/main/kotlin/de/thk/gm/gdw/todolist/controllers/UsersRestController.kt
@@ -1,7 +1,9 @@
 package de.thk.gm.gdw.todolist.controllers
 
+import de.thk.gm.gdw.todolist.dtos.UserDto
 import de.thk.gm.gdw.todolist.models.User
 import de.thk.gm.gdw.todolist.services.UsersService
+import jakarta.validation.Valid
 import org.springframework.http.HttpStatus
 import org.springframework.http.MediaType
 import org.springframework.web.bind.annotation.*
@@ -14,9 +16,10 @@ class UsersRestController (private val usersService: UsersService) {
 
     @PostMapping("/users")
     @ResponseStatus(HttpStatus.CREATED)
-    fun saveUser(@RequestParam email : String): User {
+    fun saveUser(@Valid @RequestBody userDto: UserDto): User {
         var user = User()
-        user.email = email
+        user.email = userDto.email
+        user.birthday = userDto.birthday
         usersService.saveUser(user)
         return user
     }
@@ -38,10 +41,10 @@ class UsersRestController (private val usersService: UsersService) {
 
     @PutMapping("/users/{id}")
     @ResponseStatus(HttpStatus.NO_CONTENT)
-    fun updateUser(email: String, @PathVariable id: UUID) {
+    fun updateUser(@Valid @RequestBody userDto: UserDto, @PathVariable id: UUID) {
         var user = usersService.getUserById(id)
         if (user != null) {
-            user.email = email
+            user.email = userDto.email
             usersService.saveUser(user)
         } else {
             throw ResponseStatusException(HttpStatus.NOT_FOUND)
diff --git a/src/main/kotlin/de/thk/gm/gdw/todolist/dtos/TaskDto.kt b/src/main/kotlin/de/thk/gm/gdw/todolist/dtos/TaskDto.kt
new file mode 100644
index 0000000..5a771c0
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/gdw/todolist/dtos/TaskDto.kt
@@ -0,0 +1,9 @@
+package de.thk.gm.gdw.todolist.dtos
+
+import jakarta.validation.constraints.Size
+
+class TaskDto {
+    @Size(min = 1, max = 50)
+    var name: String = ""
+    var open: Boolean = true
+}
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/gdw/todolist/dtos/UserDto.kt b/src/main/kotlin/de/thk/gm/gdw/todolist/dtos/UserDto.kt
new file mode 100644
index 0000000..33477d6
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/gdw/todolist/dtos/UserDto.kt
@@ -0,0 +1,18 @@
+package de.thk.gm.gdw.todolist.dtos
+
+import jakarta.validation.constraints.Email
+import jakarta.validation.constraints.Past
+import jakarta.validation.constraints.PastOrPresent
+import jakarta.validation.constraints.Size
+import org.springframework.format.annotation.DateTimeFormat
+import java.time.LocalDateTime
+import java.util.*
+
+class UserDto {
+    @Email
+    @Size(min = 5, max = 50)
+    var email: String = ""
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    @PastOrPresent
+    var birthday: Date? = null
+}
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/gdw/todolist/models/User.kt b/src/main/kotlin/de/thk/gm/gdw/todolist/models/User.kt
index 4eeea08..8ad8589 100644
--- a/src/main/kotlin/de/thk/gm/gdw/todolist/models/User.kt
+++ b/src/main/kotlin/de/thk/gm/gdw/todolist/models/User.kt
@@ -3,6 +3,7 @@ package de.thk.gm.gdw.todolist.models
 import jakarta.persistence.Entity
 import jakarta.persistence.Id
 import jakarta.persistence.Table
+import java.time.LocalDateTime
 import java.util.*
 
 @Entity
@@ -11,4 +12,6 @@ class User {
     @Id
     val id : UUID = UUID.randomUUID()
     var email: String = ""
+    val createdAt: LocalDateTime = LocalDateTime.now()
+    var birthday: Date? = null
 }
\ No newline at end of file
diff --git a/src/main/resources/templates/tasks/showTask.ftlh b/src/main/resources/templates/tasks/showTask.ftlh
index 2540fb1..f9978cc 100644
--- a/src/main/resources/templates/tasks/showTask.ftlh
+++ b/src/main/resources/templates/tasks/showTask.ftlh
@@ -4,8 +4,14 @@
     <form action="/users/${task.user.id}/tasks/${task.id}" method="post">
         <label>Task name</label><br>
         <input type="text" value="${task.name}" name="name"><br>
+        <#if errors?? && errors.getFieldError("name")??>
+            ${errors.getFieldError("name")["defaultMessage"]}<br>
+        </#if>
         <label>Open?</label>
         <input type="text" value="${task.open?string}" name="open"><br>
+        <#if errors?? && errors.getFieldError("open")??>
+            ${errors.getFieldError("open")["defaultMessage"]}<br>
+        </#if>
         <input type="hidden" name="_method" value="PUT"/>
         <button>Update</button>
     </form>
diff --git a/src/main/resources/templates/tasks/showTasks.ftlh b/src/main/resources/templates/tasks/showTasks.ftlh
index f4a452c..bf8da3c 100644
--- a/src/main/resources/templates/tasks/showTasks.ftlh
+++ b/src/main/resources/templates/tasks/showTasks.ftlh
@@ -9,6 +9,9 @@
     </ul>
     <form action="/users/${user.id}/tasks" method="post">
         <input name="name" type="text">
+        <#if errors?? && errors.getFieldError("name")??>
+            ${errors.getFieldError("name")["defaultMessage"]}
+        </#if>
         <button>Create task</button>
     </form>
     <br>
diff --git a/src/main/resources/templates/users/showUsers.ftlh b/src/main/resources/templates/users/showUsers.ftlh
index c78c487..686028d 100644
--- a/src/main/resources/templates/users/showUsers.ftlh
+++ b/src/main/resources/templates/users/showUsers.ftlh
@@ -6,7 +6,16 @@
     </#list>
     </ul>
     <form action="/users" method="post">
-        <input placeholder="max.mustermann@example.org" name="email">
+        <label for="email">Email</label>
+        <input placeholder="max.mustermann@example.org" name="email" minlength="5" maxlength="50" type="email" id="email"><br>
+        <#if errors?? && errors.getFieldError("email")??>
+            ${errors.getFieldError("email")["defaultMessage"]}
+        </#if>
+        <label for="birthday">Birthday</label>
+        <input name="birthday"  type="date" id="birthday">
+        <#if errors?? && errors.getFieldError("birthday")??>
+            ${errors.getFieldError("birthday")["defaultMessage"]}
+        </#if>
         <button>Create user</button>
     </form>
 </@base.layout>
\ No newline at end of file
-- 
GitLab