Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

๐Ÿ”€ :: AI ํ†ต์‹  API ๊ตฌํ˜„ #105

Merged
merged 9 commits into from
Apr 17, 2024
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ dependencies {
// mapper
implementation(Dependencies.MAPPER)
kapt(Dependencies.MAPPER_APT)

// web_flux
implementation(Dependencies.WEB_FLUX)
}

tasks.withType<KotlinCompile> {
Expand Down
3 changes: 3 additions & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,7 @@ object Dependencies {
// mapper
const val MAPPER = "org.mapstruct:mapstruct:${DependencyVersions.MAPPER_VERSION}.Final"
const val MAPPER_APT = "org.mapstruct:mapstruct-processor:${DependencyVersions.MAPPER_VERSION}.Final"

// web_flux
const val WEB_FLUX = "org.springframework.boot:spring-boot-starter-webflux"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import andreas311.miso.common.annotation.RequestController
import andreas311.miso.domain.recyclables.adapter.input.data.request.CreateRecyclablesRequest
import andreas311.miso.domain.recyclables.adapter.input.data.request.EditRecyclablesRequest
import andreas311.miso.domain.recyclables.adapter.input.data.response.DetailRecyclablesResponse
import andreas311.miso.domain.recyclables.adapter.input.data.response.ListDetailRecyclablesResponse
import andreas311.miso.domain.recyclables.adapter.input.data.response.ListRecyclablesResponse
import andreas311.miso.domain.recyclables.adapter.input.data.response.RecyclablesResponse
import andreas311.miso.domain.recyclables.adapter.input.mapper.RecyclablesDataMapper
Expand All @@ -30,6 +31,7 @@ class RecyclablesAdapter(
private val searchRecyclablesUseCase: SearchRecyclablesUseCase,
private val detailRecyclablesUseCase: DetailRecyclablesUseCase,
private val deleteRecyclablesUseCase: DeleteRecyclablesUseCase,
private val processRecyclablesUseCase: ProcessRecyclablesUseCase
) {
@PostMapping
fun create(
Expand All @@ -39,6 +41,12 @@ class RecyclablesAdapter(
createRecyclablesUseCase.execute(recyclablesDataMapper toDto createRecyclablesRequest, multipartFile)
.let { ResponseEntity.status(HttpStatus.CREATED).build() }

@PostMapping("/process")
fun process(@RequestPart(value = "recyclables") multipartFile: MultipartFile): ResponseEntity<ListDetailRecyclablesResponse> =
processRecyclablesUseCase.execute(multipartFile)
.let { recyclablesDataMapper.toResponse(it) }
.let { ResponseEntity.status(HttpStatus.OK).body(it) }

@GetMapping("/{type}")
fun detail(@PathVariable(name = "type") recyclablesType: RecyclablesType): ResponseEntity<DetailRecyclablesResponse> =
detailRecyclablesUseCase.execute(recyclablesType)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package andreas311.miso.domain.recyclables.adapter.input.data.response

data class ListDetailRecyclablesResponse(
val recyclablesList: List<DetailRecyclablesResponse>
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package andreas311.miso.domain.recyclables.adapter.input.mapper
import andreas311.miso.domain.recyclables.adapter.input.data.request.CreateRecyclablesRequest
import andreas311.miso.domain.recyclables.adapter.input.data.request.EditRecyclablesRequest
import andreas311.miso.domain.recyclables.adapter.input.data.response.DetailRecyclablesResponse
import andreas311.miso.domain.recyclables.adapter.input.data.response.ListDetailRecyclablesResponse
import andreas311.miso.domain.recyclables.adapter.input.data.response.ListRecyclablesResponse
import andreas311.miso.domain.recyclables.adapter.input.data.response.RecyclablesResponse
import andreas311.miso.domain.recyclables.application.port.input.dto.*
Expand Down Expand Up @@ -65,4 +66,22 @@ class RecyclablesDataMapper {
)
}
)

fun toResponse(listDetailRecyclablesDto: ListDetailRecyclablesDto): ListDetailRecyclablesResponse =
ListDetailRecyclablesResponse(
listDetailRecyclablesDto.recyclablesList
.map {
DetailRecyclablesResponse(
id = it.id,
title = it.title,
subTitle = it.subTitle,
recycleMethod = it.recycleMethod,
recycleTip = it.recycleTip,
recycleCaution = it.recycleCaution,
imageUrl = it.imageUrl,
recyclablesType = it.recyclablesType,
recycleMark = it.recycleMark
)
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package andreas311.miso.domain.recyclables.application.port.input

import andreas311.miso.domain.recyclables.application.port.input.dto.ListDetailRecyclablesDto
import org.springframework.web.multipart.MultipartFile

interface ProcessRecyclablesUseCase {
fun execute(multipartFile: MultipartFile): ListDetailRecyclablesDto
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package andreas311.miso.domain.recyclables.application.port.input.dto

data class ListDetailRecyclablesDto(
val recyclablesList: List<DetailRecyclablesDto>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package andreas311.miso.domain.recyclables.application.service

import andreas311.miso.common.annotation.ReadOnlyRollbackService
import andreas311.miso.domain.recyclables.application.exception.RecyclablesNotFoundException
import andreas311.miso.domain.recyclables.application.port.input.ProcessRecyclablesUseCase
import andreas311.miso.domain.recyclables.application.port.input.dto.DetailRecyclablesDto
import andreas311.miso.domain.recyclables.application.port.input.dto.ListDetailRecyclablesDto
import andreas311.miso.domain.recyclables.application.port.output.QueryRecyclablesPort
import andreas311.miso.domain.recyclables.domain.RecyclablesType
import andreas311.miso.thirdparty.aws.s3.util.S3Util
import org.springframework.beans.factory.annotation.Value
import org.springframework.web.multipart.MultipartFile
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.client.WebClient

@ReadOnlyRollbackService
class ProcessRecyclablesService(
private val s3Util: S3Util,
private val webClient: WebClient,
private val queryRecyclablesPort: QueryRecyclablesPort
) : ProcessRecyclablesUseCase {

@Value("\${ai.url}")
private val url: String = ""

override fun execute(multipartFile: MultipartFile): ListDetailRecyclablesDto {
val imageUrl = s3Util.execute(multipartFile)

val resultList =
webClient.post()
.uri(url)
.body(BodyInserters.fromValue(mapOf("imageURL" to imageUrl)))
.retrieve()
.bodyToMono(List::class.java)
.map { it as List<String> }
.block() ?: throw RecyclablesNotFoundException()

val detailRecyclablesList = resultList.map { recyclablesType ->
queryRecyclablesPort.findByRecyclablesTypeOrNull(RecyclablesType.valueOf(recyclablesType))
}

val recyclablesList = detailRecyclablesList.filterNotNull().map { DetailRecyclablesDto(it) }

return ListDetailRecyclablesDto(recyclablesList)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package andreas311.miso.global.webclient

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.client.reactive.ClientHttpConnector
import org.springframework.http.client.reactive.ReactorClientHttpConnector
import org.springframework.web.reactive.function.client.ExchangeStrategies
import org.springframework.web.reactive.function.client.WebClient
import reactor.netty.http.client.HttpClient
import java.time.Duration

@Configuration
class WebClientConfig {
@Bean
fun webClient(): WebClient {
val httpClient = HttpClient.create().responseTimeout(Duration.ofMinutes(1))
val connector: ClientHttpConnector = ReactorClientHttpConnector(httpClient)

return WebClient.builder()
.clientConnector(connector)
.exchangeStrategies(ExchangeStrategies.builder()
.codecs { configurer ->
configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024)
}
.build())
.build()
}
}
Loading