Skip to content

Commit 9262328

Browse files
committed
[DPMBE-68] 유저 프로필 이미지 업로드 기능을 만든다 (#89)
* feat: 유저 프로필 presignedUrl 발급 기능 추가 * test: 유저 프로필 presignedUrl 발급 기능 테스트 코드 추가 * feat: 유저 프로필 업로드 성공 확인 기능 추가 * test: 유저 프로필 업로드 성공 확인 기능 테스트 코드 추가 * style: spotless
1 parent e42464a commit 9262328

File tree

19 files changed

+255
-34
lines changed

19 files changed

+255
-34
lines changed

Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt

+8
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,12 @@ class ImageController(
2727
): ImageUrlResponse {
2828
return getPresignedUrlUseCase.forPromise(promiseId, fileExtension)
2929
}
30+
31+
@Operation(summary = "유저 프로필 이미지 업로드 Presigned URL 발급")
32+
@GetMapping("/users/me/images")
33+
fun getPresignedUrlOfUser(
34+
@RequestParam fileExtension: ImageFileExtension,
35+
): ImageUrlResponse {
36+
return getPresignedUrlUseCase.forUser(fileExtension)
37+
}
3038
}

Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCase.kt

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.depromeet.whatnow.annotation.UseCase
44
import com.depromeet.whatnow.api.image.dto.ImageUrlResponse
55
import com.depromeet.whatnow.config.s3.ImageFileExtension
66
import com.depromeet.whatnow.config.s3.S3UploadPresignedUrlService
7+
import com.depromeet.whatnow.config.security.SecurityUtils
78

89
@UseCase
910
class GetPresignedUrlUseCase(
@@ -12,4 +13,9 @@ class GetPresignedUrlUseCase(
1213
fun forPromise(promiseId: Long, fileExtension: ImageFileExtension): ImageUrlResponse {
1314
return ImageUrlResponse.from(presignedUrlService.forPromise(promiseId, fileExtension))
1415
}
16+
17+
fun forUser(fileExtension: ImageFileExtension): ImageUrlResponse {
18+
val currentUserId = SecurityUtils.currentUserId
19+
return ImageUrlResponse.from(presignedUrlService.forUser(currentUserId, fileExtension))
20+
}
1521
}

Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/picture/controller/PictureController.kt

+8-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ class PictureController(
2121

2222
@Operation(summary = "약속 관련 이미지 업로드 성공 요청")
2323
@PostMapping("/promises/{promiseId}/images/success/{imageKey}")
24-
fun successUploadImage(@PathVariable promiseId: Long, @PathVariable imageKey: String, @RequestParam pictureCommentType: PictureCommentType) {
25-
successUseCase.successUploadImage(promiseId, imageKey, pictureCommentType)
24+
fun promiseUploadImageSuccess(@PathVariable promiseId: Long, @PathVariable imageKey: String, @RequestParam pictureCommentType: PictureCommentType) {
25+
successUseCase.promiseUploadImageSuccess(promiseId, imageKey, pictureCommentType)
26+
}
27+
28+
@Operation(summary = "유저 프로필 이미지 업로드 성공 요청")
29+
@PostMapping("/users/me/images/success/{imageKey}")
30+
fun userUploadImageSuccess(@PathVariable imageKey: String) {
31+
successUseCase.userUploadImageSuccess(imageKey)
2632
}
2733
}

Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/picture/usecase/PictureUploadSuccessUseCase.kt

+7-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ import com.depromeet.whatnow.domains.picture.service.PictureDomainService
99
class PictureUploadSuccessUseCase(
1010
val pictureDomainService: PictureDomainService,
1111
) {
12-
fun successUploadImage(promiseId: Long, imageKey: String, pictureCommentType: PictureCommentType) {
12+
fun promiseUploadImageSuccess(promiseId: Long, imageKey: String, pictureCommentType: PictureCommentType) {
1313
val currentUserId: Long = SecurityUtils.currentUserId
14-
pictureDomainService.successUploadImage(currentUserId, promiseId, imageKey, pictureCommentType)
14+
pictureDomainService.promiseUploadImageSuccess(currentUserId, promiseId, imageKey, pictureCommentType)
15+
}
16+
17+
fun userUploadImageSuccess(imageKey: String) {
18+
val currentUserId: Long = SecurityUtils.currentUserId
19+
pictureDomainService.userUploadImageSuccess(currentUserId, imageKey)
1520
}
1621
}

Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/controller/ImageControllerTest.kt

+15-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class ImageControllerTest {
2323
lateinit var mockMvc: MockMvc
2424

2525
@Test
26-
fun `presignedUrl 요청에 성공하면 200을 응답한다`() {
26+
fun `약속 이미지 presignedUrl 요청에 성공하면 200을 응답한다`() {
2727
// given
2828
val promiseId = 1L
2929
val fileExtension = ImageFileExtension.JPEG.name
@@ -36,4 +36,18 @@ class ImageControllerTest {
3636
.andExpect(status().isOk)
3737
.andDo { print(it) }
3838
}
39+
40+
@Test
41+
fun `유저 프로필 presignedUrl 요청에 성공하면 200을 응답한다`() {
42+
// given
43+
val fileExtension = ImageFileExtension.JPEG.name
44+
45+
// when, then
46+
mockMvc.perform(
47+
get("/v1/users/me/images")
48+
.param("fileExtension", fileExtension),
49+
)
50+
.andExpect(status().isOk)
51+
.andDo { print(it) }
52+
}
3953
}

Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCaseTest.kt

+32-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ import com.depromeet.whatnow.config.s3.ImageFileExtension
44
import com.depromeet.whatnow.config.s3.ImageUrlDto
55
import com.depromeet.whatnow.config.s3.S3UploadPresignedUrlService
66
import org.junit.jupiter.api.Assertions.assertEquals
7+
import org.junit.jupiter.api.BeforeEach
78
import org.junit.jupiter.api.Test
89
import org.junit.jupiter.api.extension.ExtendWith
910
import org.mockito.InjectMocks
1011
import org.mockito.Mock
1112
import org.mockito.junit.jupiter.MockitoExtension
1213
import org.mockito.kotlin.given
14+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
15+
import org.springframework.security.core.authority.SimpleGrantedAuthority
16+
import org.springframework.security.core.context.SecurityContextHolder
17+
import org.springframework.security.test.context.support.WithMockUser
1318

1419
@ExtendWith(MockitoExtension::class)
1520
class GetPresignedUrlUseCaseTest {
@@ -19,8 +24,16 @@ class GetPresignedUrlUseCaseTest {
1924
@InjectMocks
2025
lateinit var getPresignedUrlUseCase: GetPresignedUrlUseCase
2126

27+
@BeforeEach
28+
fun setup() {
29+
val securityContext = SecurityContextHolder.createEmptyContext()
30+
val authentication = UsernamePasswordAuthenticationToken("1", null, setOf(SimpleGrantedAuthority("ROLE_USER")))
31+
securityContext.authentication = authentication
32+
SecurityContextHolder.setContext(securityContext)
33+
}
34+
2235
@Test
23-
fun `PresignUrl 을 요청하면 url 을 반환한다`() {
36+
fun `약속 이미지 PresignUrl 을 요청하면 url 을 반환한다`() {
2437
// given
2538
given(presignedUrlService.forPromise(1, ImageFileExtension.JPEG)).willReturn(
2639
ImageUrlDto(
@@ -35,4 +48,22 @@ class GetPresignedUrlUseCaseTest {
3548
assertEquals("https://whatnow.kr/1.jpg", imageUrlResponse.presignedUrl)
3649
assertEquals("1.jpg", imageUrlResponse.key)
3750
}
51+
52+
@Test
53+
@WithMockUser(username = "1")
54+
fun `유저 프로필 PresignUrl 을 요청하면 url 을 반환한다`() {
55+
// given
56+
given(presignedUrlService.forUser(1, ImageFileExtension.JPEG)).willReturn(
57+
ImageUrlDto(
58+
url = "https://whatnow.kr/1.jpg",
59+
key = "1.jpg",
60+
),
61+
)
62+
// when
63+
val imageUrlResponse = getPresignedUrlUseCase.forUser(ImageFileExtension.JPEG)
64+
65+
// then
66+
assertEquals("https://whatnow.kr/1.jpg", imageUrlResponse.presignedUrl)
67+
assertEquals("1.jpg", imageUrlResponse.key)
68+
}
3869
}

Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/picture/controller/PictureControllerTest.kt

+14-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class PictureControllerTest {
2323
lateinit var mockMvc: MockMvc
2424

2525
@Test
26-
fun `이미지 업로드 성공 요청에 정상적으로 200을 반환한다`() {
26+
fun `약속 이미지 업로드 성공 요청에 정상적으로 200을 반환한다`() {
2727
// given
2828
val promiseId = 1
2929
val imageKey = "imageKey"
@@ -37,4 +37,17 @@ class PictureControllerTest {
3737
.andExpect(status().isOk)
3838
.andDo { print(it) }
3939
}
40+
41+
@Test
42+
fun `유저 프로필 업로드 성공 요청에 정상적으로 200을 반환한다`() {
43+
// given
44+
val imageKey = "imageKey"
45+
46+
// when, then
47+
mockMvc.perform(
48+
post("/v1/users/me/images/success/{imageKey}", imageKey),
49+
)
50+
.andExpect(status().isOk)
51+
.andDo { print(it) }
52+
}
4053
}

Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/picture/usecase/PictureUploadSuccessUseCaseTest.kt

+14-2
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,26 @@ class PictureUploadSuccessUseCaseTest {
3030
}
3131

3232
@Test
33-
fun `이미지 업로드 성공 요청시 정상적이라면 에러가 발생하지 않는다`() {
33+
fun `약속 이미지 업로드 성공 요청시 정상적이라면 에러가 발생하지 않는다`() {
3434
// given
3535

3636
// when
3737

3838
// then
3939
assertThatCode {
40-
pictureUploadSuccessUseCase.successUploadImage(1, "1", PictureCommentType.SORRY_LATE)
40+
pictureUploadSuccessUseCase.promiseUploadImageSuccess(1, "imageKey", PictureCommentType.SORRY_LATE)
41+
}.doesNotThrowAnyException()
42+
}
43+
44+
@Test
45+
fun `유저 프로필 업로드 성공 요청시 정상적이라면 에러가 발생하지 않는다`() {
46+
// given
47+
48+
// when
49+
50+
// then
51+
assertThatCode {
52+
pictureUploadSuccessUseCase.userUploadImageSuccess("imageKey")
4153
}.doesNotThrowAnyException()
4254
}
4355
}

Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/picture/adapter/PictureAdapter.kt

+8-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ import com.depromeet.whatnow.domains.picture.repository.PictureRepository
99
class PictureAdapter(
1010
val pictureRepository: PictureRepository,
1111
) {
12-
fun save(userId: Long, promiseId: Long, imageUrl: String, imageKey: String, pictureCommentType: PictureCommentType) {
13-
val picture = Picture(userId, promiseId, imageUrl, imageKey, pictureCommentType)
14-
pictureRepository.save(picture)
12+
fun saveForPromise(userId: Long, promiseId: Long, imageUrl: String, imageKey: String, pictureCommentType: PictureCommentType): Picture {
13+
val picture = Picture.createForPromise(userId, promiseId, imageUrl, imageKey, pictureCommentType)
14+
return pictureRepository.save(picture)
15+
}
16+
17+
fun saveForUser(userId: Long, imageUrl: String, imageKey: String): Picture {
18+
val picture = Picture.createForUser(userId, imageUrl, imageKey)
19+
return pictureRepository.save(picture)
1520
}
1621
}

Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/picture/domain/Picture.kt

+28-1
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,40 @@ class Picture(
2525
var uuid: String,
2626

2727
@Enumerated(EnumType.STRING)
28-
var pictureCommentType: PictureCommentType,
28+
var pictureType: PictureType,
29+
30+
@Enumerated(EnumType.STRING)
31+
var pictureCommentType: PictureCommentType = PictureCommentType.NONE,
2932

3033
@Id
3134
@GeneratedValue(strategy = GenerationType.IDENTITY)
3235
@Column(name = "picture_id")
3336
val id: Long? = null,
3437
) : BaseTimeEntity() {
38+
companion object {
39+
fun createForPromise(userId: Long, promiseId: Long, url: String, uuid: String, pictureCommentType: PictureCommentType): Picture {
40+
return Picture(
41+
userId = userId,
42+
promiseId = promiseId,
43+
url = url,
44+
uuid = uuid,
45+
pictureType = PictureType.PROMISE,
46+
pictureCommentType = pictureCommentType,
47+
)
48+
}
49+
50+
fun createForUser(userId: Long, url: String, uuid: String): Picture {
51+
return Picture(
52+
userId = userId,
53+
promiseId = 0,
54+
url = url,
55+
uuid = uuid,
56+
pictureType = PictureType.USER,
57+
pictureCommentType = PictureCommentType.NONE,
58+
)
59+
}
60+
}
61+
3562
@PostPersist
3663
fun createPictureEvent() {
3764
Events.raise(PictureRegisterEvent(userId, promiseId))

Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/picture/domain/PictureCommentType.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package com.depromeet.whatnow.domains.picture.domain
22

33
import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUserType
44

5-
enum class PictureCommentType(val value: String, val promiseUserType: PromiseUserType) {
5+
enum class PictureCommentType(val value: String, val promiseUserType: PromiseUserType?) {
6+
NONE("NONE", null),
7+
68
// Can LATE
79
RUNNING("달려가는 중️", PromiseUserType.LATE),
810
GASPING("헐레벌떡", PromiseUserType.LATE),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.depromeet.whatnow.domains.picture.domain
2+
3+
enum class PictureType {
4+
PROMISE, USER
5+
}

Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/picture/service/PictureDomainService.kt

+11-2
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,30 @@ import com.depromeet.whatnow.domains.picture.exception.InvalidCommentTypeExcepti
88
import com.depromeet.whatnow.domains.picture.exception.UploadBeforeTrackingException
99
import com.depromeet.whatnow.domains.promiseuser.adaptor.PromiseUserAdaptor
1010
import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUserType
11+
import com.depromeet.whatnow.domains.user.adapter.UserAdapter
1112
import org.springframework.stereotype.Service
1213
import org.springframework.transaction.annotation.Transactional
1314

1415
@Service
1516
class PictureDomainService(
1617
val pictureAdapter: PictureAdapter,
1718
val promiseUserAdapter: PromiseUserAdaptor,
19+
val userAdapter: UserAdapter,
1820
) {
1921
@Transactional
20-
fun successUploadImage(userId: Long, promiseId: Long, imageKey: String, pictureCommentType: PictureCommentType) {
22+
fun promiseUploadImageSuccess(userId: Long, promiseId: Long, imageKey: String, pictureCommentType: PictureCommentType) {
2123
val promiseUser = promiseUserAdapter.findByPromiseIdAndUserId(promiseId, userId)
2224
validatePromiseUserType(promiseUser.promiseUserType!!, pictureCommentType)
2325

2426
val imageUrl = IMAGE_DOMAIN + "promise/$promiseId/$imageKey"
25-
pictureAdapter.save(userId, promiseId, imageUrl, imageKey, pictureCommentType)
27+
pictureAdapter.saveForPromise(userId, promiseId, imageUrl, imageKey, pictureCommentType)
28+
}
29+
30+
fun userUploadImageSuccess(userId: Long, imageKey: String) {
31+
val user = userAdapter.queryUser(userId)
32+
val imageUrl = IMAGE_DOMAIN + "user/$userId/$imageKey"
33+
pictureAdapter.saveForUser(userId, imageUrl, imageKey)
34+
user.updateProfileImg(imageUrl)
2635
}
2736

2837
private fun validatePromiseUserType(promiseUserType: PromiseUserType, pictureCommentType: PictureCommentType) {

Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/user/domain/User.kt

+8
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ class User(
9898
profileImg = profileImage
9999
nickname = username
100100
}
101+
102+
fun updateProfileImg(imageUrl: String) {
103+
if (profileImg != imageUrl) {
104+
isDefaultImg = false
105+
}
106+
profileImg = imageUrl
107+
}
108+
101109
fun toUserInfoVo(): UserInfoVo {
102110
return UserInfoVo.from(this)
103111
}

Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/picture/adapter/PictureAdapterTest.kt

+30-6
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ package com.depromeet.whatnow.domains.picture.adapter
22

33
import com.depromeet.whatnow.domains.picture.domain.Picture
44
import com.depromeet.whatnow.domains.picture.domain.PictureCommentType
5+
import com.depromeet.whatnow.domains.picture.domain.PictureType
56
import com.depromeet.whatnow.domains.picture.repository.PictureRepository
67
import org.junit.jupiter.api.Test
78
import org.junit.jupiter.api.extension.ExtendWith
8-
import org.mockito.ArgumentCaptor
99
import org.mockito.InjectMocks
1010
import org.mockito.Mock
1111
import org.mockito.Mockito
1212
import org.mockito.junit.jupiter.MockitoExtension
13-
import org.mockito.kotlin.then
13+
import org.mockito.kotlin.given
14+
import kotlin.test.assertEquals
1415

1516
@ExtendWith(MockitoExtension::class)
1617
class PictureAdapterTest {
@@ -21,13 +22,36 @@ class PictureAdapterTest {
2122
lateinit var pictureAdapter: PictureAdapter
2223

2324
@Test
24-
fun `Picture 저장 시 정상적으로 저장된다`() {
25-
val captor: ArgumentCaptor<Picture> = ArgumentCaptor.forClass(Picture::class.java)
25+
fun `약속 이미지 Picture 저장 시 정상적으로 저장된다`() {
26+
given(pictureRepository.save(Mockito.any(Picture::class.java)))
27+
.willReturn(Picture.createForPromise(1, 1, "imageUrl", "imageKey", PictureCommentType.RUNNING))
2628

2729
// when
28-
pictureAdapter.save(1, 1, "imageUrl", "imageKey", PictureCommentType.RUNNING)
30+
val picture = pictureAdapter.saveForPromise(1, 1, "imageUrl", "imageKey", PictureCommentType.RUNNING)
2931

3032
// then
31-
then(pictureRepository).should(Mockito.times(1)).save(captor.capture())
33+
assertEquals(picture.userId, 1)
34+
assertEquals(picture.promiseId, 1)
35+
assertEquals(picture.url, "imageUrl")
36+
assertEquals(picture.uuid, "imageKey")
37+
assertEquals(picture.pictureType, PictureType.PROMISE)
38+
assertEquals(picture.pictureCommentType, PictureCommentType.RUNNING)
39+
}
40+
41+
@Test
42+
fun `유저 프로필 Picture 저장 시 정상적으로 저장된다`() {
43+
given(pictureRepository.save(Mockito.any(Picture::class.java)))
44+
.willReturn(Picture.createForUser(1, "imageUrl", "imageKey"))
45+
46+
// when
47+
val picture = pictureAdapter.saveForUser(1, "imageUrl", "imageKey")
48+
49+
// then
50+
assertEquals(picture.userId, 1)
51+
assertEquals(picture.promiseId, 0)
52+
assertEquals(picture.url, "imageUrl")
53+
assertEquals(picture.uuid, "imageKey")
54+
assertEquals(picture.pictureType, PictureType.USER)
55+
assertEquals(picture.pictureCommentType, PictureCommentType.NONE)
3256
}
3357
}

0 commit comments

Comments
 (0)