Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import com.pida.client.aws.config.AwsProperties
import com.pida.client.aws.s3.AwsS3Client
import com.pida.support.aws.ImageS3Caller
import com.pida.support.aws.PresignedUrlRateLimiter
import com.pida.support.aws.S3ImageInfo
import com.pida.support.aws.S3ImageUrl
import org.springframework.stereotype.Component
import java.time.Duration
import java.time.LocalDateTime
import java.time.ZoneId

@Component
class ImageS3Processor(
Expand Down Expand Up @@ -35,15 +38,15 @@ class ImageS3Processor(

return S3ImageUrl(
presignedUrl,
presignedGet(imageFilePath, imageFileName),
generateGetUrl(imageFilePath, imageFileName),
)
}

override suspend fun getImageUrl(
prefix: String,
prefixId: Long,
fileName: String?,
): List<String> {
): List<S3ImageInfo> {
val imageFilePath = imageFileConstructor.imageFilePath(prefix, prefixId)

return fileName
Expand All @@ -52,7 +55,7 @@ class ImageS3Processor(
} ?: listPresignedGets(imageFilePath) // ์•„๋‹ˆ๋ฉด ํ•ด๋‹น ๊ฒฝ๋กœ ์•„๋ž˜ ๋ชจ๋“  ์ด๋ฏธ์ง€ ํƒ์ƒ‰
}

private fun presignedGet(
private fun generateGetUrl(
filePath: String,
fileName: String,
ttl: Duration = Duration.ofSeconds(30),
Expand All @@ -64,10 +67,33 @@ class ImageS3Processor(
ttl = ttl,
)

private fun presignedGet(
filePath: String,
fileName: String,
ttl: Duration = Duration.ofSeconds(30),
): S3ImageInfo {
val url =
awsS3Client.generateUrl(
bucketName = awsProperties.s3.bucket,
filePath = filePath,
fileName = fileName,
ttl = ttl,
)
val lastModified =
awsS3Client.getObjectLastModified(
bucketName = awsProperties.s3.bucket,
key = "$filePath/$fileName",
)
return S3ImageInfo(
url = url,
uploadedAt = LocalDateTime.ofInstant(lastModified, ZoneId.of("Asia/Seoul")),
)
}

private fun listPresignedGets(
filePath: String,
ttl: Duration = Duration.ofSeconds(30),
): List<String> =
): List<S3ImageInfo> =
awsS3Client
.getBucketListObjects(
bucketName = awsProperties.s3.bucket,
Expand All @@ -76,7 +102,17 @@ class ImageS3Processor(
.orEmpty()
.asSequence()
.filterNot { it.key().endsWith("/") }
.map { it.key().substringAfterLast("/") }
.map { presignedGet(filePath, it, ttl) }
.toList()
.map { s3Object ->
val fileName = s3Object.key().substringAfterLast("/")
S3ImageInfo(
url =
awsS3Client.generateUrl(
bucketName = awsProperties.s3.bucket,
filePath = filePath,
fileName = fileName,
ttl = ttl,
),
uploadedAt = LocalDateTime.ofInstant(s3Object.lastModified(), ZoneId.of("Asia/Seoul")),
)
}.toList()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.pida.client.aws.s3
import org.springframework.stereotype.Component
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.model.GetObjectRequest
import software.amazon.awssdk.services.s3.model.HeadObjectRequest
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request
import software.amazon.awssdk.services.s3.model.ListObjectsV2Response
import software.amazon.awssdk.services.s3.model.PutObjectRequest
Expand All @@ -12,6 +13,7 @@ import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignReques
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest
import java.nio.charset.Charset
import java.time.Duration
import java.time.Instant

@Component
class AwsS3Client(
Expand Down Expand Up @@ -111,6 +113,19 @@ class AwsS3Client(
.build()
}

fun getObjectLastModified(
bucketName: String,
key: String,
): Instant {
val request =
HeadObjectRequest
.builder()
.bucket(bucketName)
.key(key)
.build()
return s3Client.headObject(request).lastModified()
}

fun getObjectAsBytes(
bucketName: String,
filePath: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ data class FlowerSpotResponseDto(
pinPoint = flowerSpot.pinPoint,
region = flowerSpot.region,
kind = flowerSpot.kind,
previewUrl = flowerSpot.imageUrls.firstOrNull(),
previewUrl = flowerSpot.images.firstOrNull()?.url,
deletedAt = flowerSpot.deletedAt,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,8 @@ data class FlowerSpotDetailsResponse(
val region: Region,
@Schema(description = "๊ฝƒ ์ข…๋ฅ˜", example = "BLOSSOM")
val kind: FlowerKind,
@Schema(
description = "์ด๋ฏธ์ง€ URL ๋ชฉ๋ก",
example = "[\"https://example.com/image1.jpg\", \"https://example.com/image2.jpg\"]",
)
val imageUrls: List<String> = emptyList(),
@Schema(description = "์ด๋ฏธ์ง€ ๋ชฉ๋ก")
val imageUrls: List<FlowerSpotImageResponse> = emptyList(),
@Schema(description = "์‚ญ์ œ ์ผ์ž", example = "2025-04-01T00:00:00")
val deletedAt: LocalDateTime?,
) {
Expand All @@ -74,7 +71,7 @@ data class FlowerSpotDetailsResponse(
pinPoint = flowerSpotDetails.pinPoint,
region = flowerSpotDetails.region,
kind = flowerSpotDetails.kind,
imageUrls = flowerSpotDetails.imageUrls,
imageUrls = flowerSpotDetails.images.map { FlowerSpotImageResponse.from(it) },
deletedAt = flowerSpotDetails.deletedAt,
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.pida.presentation.v1.flowerspot.response

import com.pida.flowerspot.FlowerSpotImage
import io.swagger.v3.oas.annotations.media.Schema
import java.time.LocalDateTime

@Schema(description = "๋ฒš๊ฝƒ๊ธธ ์ด๋ฏธ์ง€ ์‘๋‹ต")
data class FlowerSpotImageResponse(
@field:Schema(description = "์ด๋ฏธ์ง€ URL", example = "https://example.com/image1.jpg")
val url: String,
@field:Schema(description = "๋“ฑ๋ก ์ผ์ž", example = "2025-04-01T12:00:00")
val createdAt: LocalDateTime,
) {
companion object {
fun from(image: FlowerSpotImage) =
FlowerSpotImageResponse(
url = image.url,
createdAt = image.createdAt,
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.pida.presentation.v1.place.response

import com.fasterxml.jackson.annotation.JsonInclude
import com.pida.flowerspot.FlowerSpot
import com.pida.place.District
import com.pida.place.Landmark
Expand Down Expand Up @@ -37,22 +38,30 @@ data class PlaceSearchResultResponse(

@Schema(description = "๊ณตํ†ต ์žฅ์†Œ ์‘๋‹ต")
data class PlaceSearchResponse(
@Schema(description = "์žฅ์†Œ ์ด๋ฆ„", example = "์—ฌ์˜๋„ ํ•œ๊ฐ•๊ณต์›")
@field:Schema(description = "์žฅ์†Œ ์ด๋ฆ„", example = "์—ฌ์˜๋„ ํ•œ๊ฐ•๊ณต์›", requiredMode = Schema.RequiredMode.REQUIRED)
val name: String,
@Schema(description = "์ฃผ์†Œ", example = "์„œ์šธํŠน๋ณ„์‹œ ์˜๋“ฑํฌ๊ตฌ ์—ฌ์˜๋„๋™")
@field:Schema(description = "์ฃผ์†Œ", example = "์„œ์šธํŠน๋ณ„์‹œ ์˜๋“ฑํฌ๊ตฌ ์—ฌ์˜๋„๋™", requiredMode = Schema.RequiredMode.REQUIRED)
val address: String?,
@Schema(
@field:Schema(
description = "ํ•€ ํฌ์ธํŠธ ์ •๋ณด (GeoJson)",
example = """
{
"type": "Point",
"coordinates": [126.9340, 37.5284]
}
""",
requiredMode = Schema.RequiredMode.REQUIRED,
)
val pinPoint: GeoJson,
@Schema(description = "์ง€์—ญ", example = "SEOUL")
@field:Schema(description = "์ง€์—ญ", example = "SEOUL", requiredMode = Schema.RequiredMode.REQUIRED)
val region: Region,
@field:JsonInclude(JsonInclude.Include.NON_NULL)
@field:Schema(
description = "๋ฒš๊ฝƒ๊ธธ ID (๋ฒš๊ฝƒ๊ธธ์ธ ๊ฒฝ์šฐ์—๋งŒ ํฌํ•จ)",
example = "1",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
)
val flowerSpotId: Long? = null,
) {
companion object {
fun from(landmark: Landmark) =
Expand All @@ -69,13 +78,19 @@ data class PlaceSearchResponse(
address = flowerSpot.address,
pinPoint = flowerSpot.pinPoint,
region = flowerSpot.region,
flowerSpotId = flowerSpot.id,
)

fun from(district: District) =
PlaceSearchResponse(
name =
listOfNotNull(district.sido, district.sigungu, district.eupmyeondonggu, district.eupmyeonridong, district.ri)
.last(),
listOfNotNull(
district.sido,
district.sigungu,
district.eupmyeondonggu,
district.eupmyeonridong,
district.ri,
).last(),
address =
listOfNotNull(district.sigungu, district.eupmyeondonggu, district.eupmyeonridong, district.ri)
.takeIf { it.isNotEmpty() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.pida.flowerspot

import com.pida.blooming.Blooming
import com.pida.blooming.BloomingStatus
import com.pida.support.aws.S3ImageInfo
import com.pida.support.geo.GeoJson
import com.pida.support.geo.Region
import java.time.LocalDateTime
Expand All @@ -18,14 +19,14 @@ data class FlowerSpotDetails(
val pinPoint: GeoJson, // Point GeoJson
val region: Region,
val kind: FlowerKind,
val imageUrls: List<String> = emptyList(),
val images: List<FlowerSpotImage> = emptyList(),
val deletedAt: LocalDateTime?,
) {
companion object {
fun of(
flowerSpot: FlowerSpot,
bloomings: List<Blooming>,
imageUrls: List<String> = emptyList(),
images: List<S3ImageInfo> = emptyList(),
) = FlowerSpotDetails(
id = flowerSpot.id,
address = flowerSpot.address,
Expand All @@ -38,7 +39,7 @@ data class FlowerSpotDetails(
pinPoint = flowerSpot.pinPoint,
region = flowerSpot.region,
kind = flowerSpot.kind,
imageUrls = imageUrls,
images = images.map { FlowerSpotImage(url = it.url, createdAt = it.uploadedAt) },
deletedAt = flowerSpot.deletedAt,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class FlowerSpotFacade(
coroutineScope {
val flowerSpotDeferred = async { flowerSpotService.readOneFlowerSpot(spotId) }
val bloomings = async { bloomingService.recentlyBloomingBySpotId(spotId) }
val imageUrls =
val images =
async {
imageS3Caller.getImageUrl(
prefix = ImagePrefix.FLOWERSPOT.value,
Expand All @@ -30,7 +30,7 @@ class FlowerSpotFacade(
return@coroutineScope FlowerSpotDetails.of(
flowerSpot = flowerSpotDeferred.await(),
bloomings = bloomings.await().groupBy { it.flowerSpotId }[spotId] ?: emptyList(),
imageUrls = imageUrls.await(),
images = images.await(),
)
}

Expand All @@ -45,7 +45,7 @@ class FlowerSpotFacade(
FlowerSpotDetails.of(
flowerSpot = flowerSpot,
bloomings = recentlyBlooming.groupBy { it.flowerSpotId }[flowerSpot.id] ?: emptyList(),
imageUrls =
images =
imageS3Caller.getImageUrl(
prefix = ImagePrefix.FLOWERSPOT.value,
prefixId = flowerSpot.id,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.pida.flowerspot

import java.time.LocalDateTime

data class FlowerSpotImage(
val url: String,
val createdAt: LocalDateTime,
)
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ interface ImageS3Caller {
prefix: String,
prefixId: Long,
fileName: String?,
): List<String>
): List<S3ImageInfo>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.pida.support.aws

import java.time.LocalDateTime

data class S3ImageInfo(
val url: String,
val uploadedAt: LocalDateTime,
)
Loading