11package com.example.demo
22
3+ import jakarta.validation.Valid
4+ import jakarta.validation.constraints.NotBlank
5+ import jakarta.validation.constraints.NotNull
6+ import jakarta.validation.constraints.Size
7+ import org.springframework.http.HttpStatus
38import org.springframework.jdbc.core.simple.JdbcClient
9+ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
10+ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
11+ import org.springframework.jdbc.support.GeneratedKeyHolder
12+ import org.springframework.web.bind.annotation.PostMapping
13+ import org.springframework.web.bind.annotation.RequestBody
414import org.springframework.web.bind.annotation.GetMapping
515import org.springframework.web.bind.annotation.RequestMapping
16+ import org.springframework.web.bind.annotation.ResponseStatus
617import org.springframework.web.bind.annotation.RestController
18+ import java.time.LocalDate
719
820@RestController
921@RequestMapping(" /api/tasks" )
@@ -13,34 +25,130 @@ class SampleTaskController(
1325
1426 @GetMapping
1527 fun listTasks (): List <SampleTaskResponse > = sampleTaskService.findAll()
28+
29+ @PostMapping
30+ @ResponseStatus(HttpStatus .CREATED )
31+ fun createTask (@Valid @RequestBody request : CreateBusinessTaskRequest ): SampleTaskResponse {
32+ return sampleTaskService.createTask(request)
33+ }
1634}
1735
1836@org.springframework.stereotype.Service
1937class SampleTaskService (
20- private val jdbcClient : JdbcClient
38+ private val jdbcClient : JdbcClient ,
39+ private val namedParameterJdbcTemplate : NamedParameterJdbcTemplate
2140) {
2241
2342 fun findAll (): List <SampleTaskResponse > {
2443 return jdbcClient.sql(
2544 """
26- SELECT id, title, status, created_at
45+ SELECT id, title, status, created_at, customer_request, requested_work,
46+ target_delivery_date, build_estimate, owner_name
2747 FROM sample_task
2848 ORDER BY id
2949 """ .trimIndent()
30- ).query { rs, _ ->
31- SampleTaskResponse (
32- id = rs.getLong(" id" ),
33- title = rs.getString(" title" ),
34- status = rs.getString(" status" ),
35- createdAt = rs.getTimestamp(" created_at" ).toInstant().toString()
50+ ).query(::mapTask).list()
51+ }
52+
53+ fun createTask (request : CreateBusinessTaskRequest ): SampleTaskResponse {
54+ val normalizedRequestedWork = request.requestedWork.requireText()
55+ val keyHolder = GeneratedKeyHolder ()
56+
57+ namedParameterJdbcTemplate.update(
58+ """
59+ INSERT INTO sample_task (
60+ title,
61+ status,
62+ customer_request,
63+ requested_work,
64+ target_delivery_date,
65+ build_estimate,
66+ owner_name
67+ ) VALUES (
68+ :title,
69+ :status,
70+ :customerRequest,
71+ :requestedWork,
72+ :targetDeliveryDate,
73+ :buildEstimate,
74+ :ownerName
3675 )
37- }.list()
76+ """ .trimIndent(),
77+ MapSqlParameterSource ()
78+ .addValue(" title" , toTitle(normalizedRequestedWork))
79+ .addValue(" status" , " TODO" )
80+ .addValue(" customerRequest" , request.customerRequest.requireText())
81+ .addValue(" requestedWork" , normalizedRequestedWork)
82+ .addValue(" targetDeliveryDate" , requireNotNull(request.targetDeliveryDate))
83+ .addValue(" buildEstimate" , request.buildEstimate.requireText())
84+ .addValue(" ownerName" , request.owner.requireText()),
85+ keyHolder,
86+ arrayOf(" id" )
87+ )
88+
89+ val taskId = keyHolder.key?.toLong() ? : error(" Task insert did not return an ID" )
90+ return findById(taskId) ? : error(" Task $taskId was created but could not be read back" )
91+ }
92+
93+ private fun findById (id : Long ): SampleTaskResponse ? {
94+ return jdbcClient.sql(
95+ """
96+ SELECT id, title, status, created_at, customer_request, requested_work,
97+ target_delivery_date, build_estimate, owner_name
98+ FROM sample_task
99+ WHERE id = :id
100+ """ .trimIndent()
101+ ).param(" id" , id).query(::mapTask).optional().orElse(null )
102+ }
103+
104+ @Suppress(" UNUSED_PARAMETER" )
105+ private fun mapTask (rs : java.sql.ResultSet , rowNumber : Int ): SampleTaskResponse {
106+ return SampleTaskResponse (
107+ id = rs.getLong(" id" ),
108+ title = rs.getString(" title" ),
109+ status = rs.getString(" status" ),
110+ createdAt = rs.getTimestamp(" created_at" ).toInstant().toString(),
111+ customerRequest = rs.getString(" customer_request" ),
112+ requestedWork = rs.getString(" requested_work" ),
113+ targetDeliveryDate = rs.getDate(" target_delivery_date" ).toLocalDate().toString(),
114+ buildEstimate = rs.getString(" build_estimate" ),
115+ owner = rs.getString(" owner_name" )
116+ )
117+ }
118+
119+ private fun toTitle (requestedWork : String ): String {
120+ val compact = requestedWork.replace(" \\ s+" .toRegex(), " " ).trim()
121+ return if (compact.length <= 100 ) compact else compact.take(97 ).trimEnd() + " ..."
38122 }
39123}
40124
125+ data class CreateBusinessTaskRequest (
126+ @field:NotBlank(message = "Customer request is required")
127+ @field:Size(max = 255, message = "Customer request must be 255 characters or fewer")
128+ val customerRequest : String? ,
129+ @field:NotBlank(message = "Requested work is required")
130+ @field:Size(max = 255, message = "Requested work must be 255 characters or fewer")
131+ val requestedWork : String? ,
132+ @field:NotNull(message = "Delivery date is required")
133+ val targetDeliveryDate : LocalDate ? ,
134+ @field:NotBlank(message = "Build estimate is required")
135+ @field:Size(max = 60, message = "Build estimate must be 60 characters or fewer")
136+ val buildEstimate : String? ,
137+ @field:NotBlank(message = "Owner is required")
138+ @field:Size(max = 80, message = "Owner must be 80 characters or fewer")
139+ val owner : String?
140+ )
141+
41142data class SampleTaskResponse (
42143 val id : Long ,
43144 val title : String ,
44145 val status : String ,
45- val createdAt : String
146+ val createdAt : String ,
147+ val customerRequest : String ,
148+ val requestedWork : String ,
149+ val targetDeliveryDate : String ,
150+ val buildEstimate : String ,
151+ val owner : String
46152)
153+
154+ private fun String?.requireText (): String = requireNotNull(this ).trim()
0 commit comments