From dfb61d01ec62e8ef49a89e6d465deefb01cfcac6 Mon Sep 17 00:00:00 2001 From: Piotr Wolak Date: Wed, 8 Mar 2023 12:43:36 +0100 Subject: [PATCH] Rewrite annotation approach to Kotlin DSL router --- .../springcoroutines/config/Config.kt | 48 +++++++++ .../controller/UserController.kt | 79 -------------- .../CompanyHandler.kt} | 75 ++++++++----- .../SearchHandler.kt} | 34 +++--- .../springcoroutines/handler/UserHandler.kt | 100 ++++++++++++++++++ 5 files changed, 217 insertions(+), 119 deletions(-) create mode 100644 src/main/kotlin/com/codersee/springcoroutines/config/Config.kt delete mode 100644 src/main/kotlin/com/codersee/springcoroutines/controller/UserController.kt rename src/main/kotlin/com/codersee/springcoroutines/{controller/CompanyController.kt => handler/CompanyHandler.kt} (58%) rename src/main/kotlin/com/codersee/springcoroutines/{controller/SearchController.kt => handler/SearchHandler.kt} (57%) create mode 100644 src/main/kotlin/com/codersee/springcoroutines/handler/UserHandler.kt diff --git a/src/main/kotlin/com/codersee/springcoroutines/config/Config.kt b/src/main/kotlin/com/codersee/springcoroutines/config/Config.kt new file mode 100644 index 0000000..039d4f0 --- /dev/null +++ b/src/main/kotlin/com/codersee/springcoroutines/config/Config.kt @@ -0,0 +1,48 @@ +package com.codersee.springcoroutines.config + +import com.codersee.springcoroutines.handler.CompanyHandler +import com.codersee.springcoroutines.handler.SearchHandler +import com.codersee.springcoroutines.handler.UserHandler +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.http.MediaType +import org.springframework.web.reactive.function.server.coRouter + +@Configuration +class Config { + + @Bean + fun router( + userHandler: UserHandler, + companyHandler: CompanyHandler, + searchHandler: SearchHandler + ) = coRouter { + accept(MediaType.APPLICATION_JSON).nest { + + "/api".nest { + + "/users".nest { + POST("", userHandler::createUser) + GET("", userHandler::findUsers) + GET("/{id}", userHandler::findUserById) + DELETE("/{id}", userHandler::deleteUserById) + PUT("/{id}", userHandler::updateUser) + } + + "/companies".nest { + POST("", companyHandler::createCompany) + GET("", companyHandler::findCompany) + GET("/{id}", companyHandler::findCompanyById) + DELETE("/{id}", companyHandler::deleteCompanyById) + PUT("/{id}", companyHandler::updateCompany) + } + + "/search".nest { + GET("", searchHandler::searchByNames) + } + } + + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/codersee/springcoroutines/controller/UserController.kt b/src/main/kotlin/com/codersee/springcoroutines/controller/UserController.kt deleted file mode 100644 index a5b5273..0000000 --- a/src/main/kotlin/com/codersee/springcoroutines/controller/UserController.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.codersee.springcoroutines.controller - -import com.codersee.springcoroutines.dto.UserRequest -import com.codersee.springcoroutines.dto.UserResponse -import com.codersee.springcoroutines.model.User -import com.codersee.springcoroutines.service.UserService -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.springframework.http.HttpStatus -import org.springframework.web.bind.annotation.* -import org.springframework.web.server.ResponseStatusException - -@RestController -@RequestMapping("/api/users") -class UserController( - private val userService: UserService -) { - - @PostMapping - suspend fun createUser(@RequestBody userRequest: UserRequest): UserResponse = - userService.saveUser( - user = userRequest.toModel() - ) - ?.toResponse() - ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected error during user creation.") - - @GetMapping - suspend fun findUsers( - @RequestParam("name", required = false) name: String? - ): Flow { - val users = name?.let { userService.findAllUsersByNameLike(name) } - ?: userService.findAllUsers() - - return users.map(User::toResponse) - } - - @GetMapping("/{id}") - suspend fun findUserById( - @PathVariable id: Long - ): UserResponse = - userService.findUserById(id) - ?.let(User::toResponse) - ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "User with id $id was not found.") - - @DeleteMapping("/{id}") - suspend fun deleteUserById( - @PathVariable id: Long - ) { - userService.deleteUserById(id) - } - - @PutMapping("/{id}") - suspend fun updateUser( - @PathVariable id: Long, - @RequestBody userRequest: UserRequest - ): UserResponse = - userService.updateUser( - id = id, - requestedUser = userRequest.toModel() - ) - .toResponse() -} - -private fun UserRequest.toModel(): User = - User( - email = this.email, - name = this.name, - age = this.age, - companyId = this.companyId - ) - -fun User.toResponse(): UserResponse = - UserResponse( - id = this.id!!, - email = this.email, - name = this.name, - age = this.age - ) - diff --git a/src/main/kotlin/com/codersee/springcoroutines/controller/CompanyController.kt b/src/main/kotlin/com/codersee/springcoroutines/handler/CompanyHandler.kt similarity index 58% rename from src/main/kotlin/com/codersee/springcoroutines/controller/CompanyController.kt rename to src/main/kotlin/com/codersee/springcoroutines/handler/CompanyHandler.kt index 106186c..184dcaa 100644 --- a/src/main/kotlin/com/codersee/springcoroutines/controller/CompanyController.kt +++ b/src/main/kotlin/com/codersee/springcoroutines/handler/CompanyHandler.kt @@ -1,4 +1,4 @@ -package com.codersee.springcoroutines.controller +package com.codersee.springcoroutines.handler import com.codersee.springcoroutines.dto.CompanyRequest import com.codersee.springcoroutines.dto.CompanyResponse @@ -6,23 +6,23 @@ import com.codersee.springcoroutines.model.Company import com.codersee.springcoroutines.model.User import com.codersee.springcoroutines.service.CompanyService import com.codersee.springcoroutines.service.UserService -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import org.springframework.http.HttpStatus -import org.springframework.web.bind.annotation.* +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.server.* import org.springframework.web.server.ResponseStatusException -@RestController -@RequestMapping("/api/companies") -class CompanyController( +@Component +class CompanyHandler( private val companyService: CompanyService, private val userService: UserService ) { - @PostMapping - suspend fun createCompany(@RequestBody companyRequest: CompanyRequest): CompanyResponse = - companyService.saveCompany( + suspend fun createCompany(request: ServerRequest): ServerResponse { + val companyRequest = request.awaitBody(CompanyRequest::class) + + val createdCompanyResponse = companyService.saveCompany( company = companyRequest.toModel() ) ?.toResponse() @@ -30,27 +30,35 @@ class CompanyController( HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected error during company creation." ) - @GetMapping + return ServerResponse.ok() + .bodyValueAndAwait(createdCompanyResponse) + } + suspend fun findCompany( - @RequestParam("name", required = false) name: String? - ): Flow { - val companies = name?.let { companyService.findAllCompaniesByNameLike(name) } + request: ServerRequest + ): ServerResponse { + val companies = request.queryParamOrNull("name") + ?.let { name -> companyService.findAllCompaniesByNameLike(name) } ?: companyService.findAllCompanies() - return companies + val companiesResponses = companies .map { company -> company.toResponse( users = findCompanyUsers(company) ) } + + return ServerResponse.ok() + .bodyAndAwait(companiesResponses) } - @GetMapping("/{id}") suspend fun findCompanyById( - @PathVariable id: Long - ): CompanyResponse = - companyService.findCompanyById(id) + request: ServerRequest + ): ServerResponse { + val id = request.pathVariable("id").toLong() + + val companyResponse = companyService.findCompanyById(id) ?.let { company -> company.toResponse( users = findCompanyUsers(company) @@ -58,19 +66,30 @@ class CompanyController( } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Company with id $id was not found.") - @DeleteMapping("/{id}") + return ServerResponse.ok() + .bodyValueAndAwait(companyResponse) + } + + suspend fun deleteCompanyById( - @PathVariable id: Long - ) { + request: ServerRequest + ): ServerResponse { + val id = request.pathVariable("id").toLong() + companyService.deleteCompanyById(id) + + return ServerResponse.noContent() + .buildAndAwait() } - @PutMapping("/{id}") suspend fun updateCompany( - @PathVariable id: Long, - @RequestBody companyRequest: CompanyRequest - ): CompanyResponse = - companyService.updateCompany( + request: ServerRequest + ): ServerResponse { + val id = request.pathVariable("id").toLong() + val companyRequest = request.awaitBody(CompanyRequest::class) + + + val updatedCompanyResponse = companyService.updateCompany( id = id, requestedCompany = companyRequest.toModel() ) @@ -80,6 +99,10 @@ class CompanyController( ) } + return ServerResponse.ok() + .bodyValueAndAwait(updatedCompanyResponse) + } + private suspend fun findCompanyUsers(company: Company) = userService.findUsersByCompanyId(company.id!!) .toList() diff --git a/src/main/kotlin/com/codersee/springcoroutines/controller/SearchController.kt b/src/main/kotlin/com/codersee/springcoroutines/handler/SearchHandler.kt similarity index 57% rename from src/main/kotlin/com/codersee/springcoroutines/controller/SearchController.kt rename to src/main/kotlin/com/codersee/springcoroutines/handler/SearchHandler.kt index 8665967..9b014b0 100644 --- a/src/main/kotlin/com/codersee/springcoroutines/controller/SearchController.kt +++ b/src/main/kotlin/com/codersee/springcoroutines/handler/SearchHandler.kt @@ -1,4 +1,4 @@ -package com.codersee.springcoroutines.controller +package com.codersee.springcoroutines.handler import com.codersee.springcoroutines.dto.IdNameTypeResponse import com.codersee.springcoroutines.dto.ResultType @@ -6,31 +6,37 @@ import com.codersee.springcoroutines.model.Company import com.codersee.springcoroutines.model.User import com.codersee.springcoroutines.service.CompanyService import com.codersee.springcoroutines.service.UserService -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController - -@RestController -@RequestMapping("/api/search") -class SearchController( +import org.springframework.http.HttpStatus +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.server.ServerRequest +import org.springframework.web.reactive.function.server.ServerResponse +import org.springframework.web.reactive.function.server.bodyAndAwait +import org.springframework.web.reactive.function.server.queryParamOrNull +import org.springframework.web.server.ResponseStatusException + +@Component +class SearchHandler( private val userService: UserService, private val companyService: CompanyService ) { - @GetMapping suspend fun searchByNames( - @RequestParam(name = "query") query: String - ): Flow { + request: ServerRequest + ): ServerResponse { + val query = request.queryParamOrNull("name") + ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST) + val usersFlow = userService.findAllUsersByNameLike(name = query) .map(User::toIdNameTypeResponse) val companiesFlow = companyService.findAllCompaniesByNameLike(name = query) .map(Company::toIdNameTypeResponse) - return merge(usersFlow, companiesFlow) + val mergedFlows = merge(usersFlow, companiesFlow) + + return ServerResponse.ok() + .bodyAndAwait(mergedFlows) } } diff --git a/src/main/kotlin/com/codersee/springcoroutines/handler/UserHandler.kt b/src/main/kotlin/com/codersee/springcoroutines/handler/UserHandler.kt new file mode 100644 index 0000000..5ab1cd0 --- /dev/null +++ b/src/main/kotlin/com/codersee/springcoroutines/handler/UserHandler.kt @@ -0,0 +1,100 @@ +package com.codersee.springcoroutines.handler + +import com.codersee.springcoroutines.dto.UserRequest +import com.codersee.springcoroutines.dto.UserResponse +import com.codersee.springcoroutines.model.User +import com.codersee.springcoroutines.service.UserService +import kotlinx.coroutines.flow.map +import org.springframework.http.HttpStatus +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.server.* +import org.springframework.web.server.ResponseStatusException + +@Component +class UserHandler( + private val userService: UserService +) { + + suspend fun createUser(request: ServerRequest): ServerResponse { + val userRequest = request.awaitBody(UserRequest::class) + + val savedUserResponse = userService.saveUser( + user = userRequest.toModel() + ) + ?.toResponse() + ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected error during user creation.") + + return ServerResponse.ok() + .bodyValueAndAwait(savedUserResponse) + } + + suspend fun findUsers( + request: ServerRequest + ): ServerResponse { + val users = request.queryParamOrNull("name") + ?.let { name -> userService.findAllUsersByNameLike(name) } + ?: userService.findAllUsers() + + val usersResponse = users.map(User::toResponse) + + return ServerResponse.ok() + .bodyAndAwait(usersResponse) + } + + suspend fun findUserById( + request: ServerRequest + ): ServerResponse { + val id = request.pathVariable("id").toLong() + + val userResponse = userService.findUserById(id) + ?.let(User::toResponse) + ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "User with id $id was not found.") + + return ServerResponse.ok() + .bodyValueAndAwait(userResponse) + } + + suspend fun deleteUserById( + request: ServerRequest + ): ServerResponse { + val id = request.pathVariable("id").toLong() + + userService.deleteUserById(id) + + return ServerResponse.noContent() + .buildAndAwait() + } + + suspend fun updateUser( + request: ServerRequest + ): ServerResponse { + val id = request.pathVariable("id").toLong() + val userRequest = request.awaitBody(UserRequest::class) + + val userResponse = userService.updateUser( + id = id, + requestedUser = userRequest.toModel() + ).toResponse() + + return ServerResponse.ok() + .bodyValueAndAwait(userResponse) + } + +} + +private fun UserRequest.toModel(): User = + User( + email = this.email, + name = this.name, + age = this.age, + companyId = this.companyId + ) + +fun User.toResponse(): UserResponse = + UserResponse( + id = this.id!!, + email = this.email, + name = this.name, + age = this.age + ) +