From f1cf9ee20be74fc96f30d4734bb9a153b7d28558 Mon Sep 17 00:00:00 2001 From: wwan13 Date: Thu, 1 Aug 2024 23:57:47 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat=20:=20=EC=9D=BC=EA=B8=B0=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=83=9D=EC=84=B1=20=ED=9A=9F=EC=88=98=20?= =?UTF-8?q?=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/org/grida/domain/diaryimage/DiaryImage.kt | 4 ++++ .../org/grida/domain/diaryimage/DiaryImageReader.kt | 9 +++++++-- .../grida/domain/diaryimage/DiaryImageRepository.kt | 2 ++ .../org/grida/domain/diaryimage/DiaryImageService.kt | 5 ++++- .../grida/domain/diaryimage/DiaryImageValidator.kt | 12 +++++++++++- .../kotlin/org/grida/error/CoreDomainErrorType.kt | 7 +++++++ .../diaryimage/DiaryImageEntityRepository.kt | 4 ++++ .../diaryimage/DiaryImageJpaEntityRepository.kt | 2 ++ 8 files changed, 41 insertions(+), 4 deletions(-) diff --git a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImage.kt b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImage.kt index b821193..dd40b9f 100644 --- a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImage.kt +++ b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImage.kt @@ -13,4 +13,8 @@ data class DiaryImage( override fun isOwner(accessorId: Long): Boolean { return userId == accessorId } + + companion object { + const val IMAGE_GENERATE_MAX_ATTEMPT_COUNT = 3 + } } diff --git a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageReader.kt b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageReader.kt index c64220c..8b3e15e 100644 --- a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageReader.kt +++ b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageReader.kt @@ -9,7 +9,12 @@ class DiaryImageReader( ) { @Transactional(readOnly = true) - fun read(diaryId: Long): DiaryImage { - return diaryImageRepository.findById(diaryId) + fun read(diaryImageId: Long): DiaryImage { + return diaryImageRepository.findById(diaryImageId) + } + + @Transactional(readOnly = true) + fun countGeneratedImages(diaryId: Long): Long { + return diaryImageRepository.countByDiaryId(diaryId) } } diff --git a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageRepository.kt b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageRepository.kt index 7690b05..ad92a80 100644 --- a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageRepository.kt +++ b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageRepository.kt @@ -9,6 +9,8 @@ interface DiaryImageRepository { fun findById(id: Long): DiaryImage + fun countByDiaryId(diaryId: Long): Long + fun existsByDiaryIdAndStatus(diaryId: Long, status: ImageStatus): Boolean fun updateStatus(diaryImageId: Long, status: ImageStatus): Long diff --git a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageService.kt b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageService.kt index 17d947c..440fc76 100644 --- a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageService.kt +++ b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageService.kt @@ -20,15 +20,18 @@ class DiaryImageService( diaryId: Long, userId: Long ): Long { + diaryImageValidator.validateRemainingAttemptCount(diaryId) + val diary = diaryReader.read(diaryId) accessManager.ownerOnly(diary, userId) - val generatedImageUrl = diaryImageGenerator.generate(diary.content) + val generatedImageUrl = diaryImageGenerator.generate(diary.content) val diaryImage = DiaryImage( userId = userId, diaryId = diaryId, image = Image(generatedImageUrl, ImageStatus.DEACTIVATE) ) + return diaryImageAppender.append(diaryImage, diaryId, userId) } diff --git a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageValidator.kt b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageValidator.kt index f354f35..9fa2e93 100644 --- a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageValidator.kt +++ b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageValidator.kt @@ -3,12 +3,14 @@ package org.grida.domain.diaryimage import org.grida.domain.image.ImageStatus import org.grida.error.ActivateImageAlreadyExists import org.grida.error.GridaException +import org.grida.error.ImageGenerateAttemptOver import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @Component class DiaryImageValidator( - private val diaryImageRepository: DiaryImageRepository + private val diaryImageRepository: DiaryImageRepository, + private val diaryImageReader: DiaryImageReader ) { @Transactional(readOnly = true) @@ -17,4 +19,12 @@ class DiaryImageValidator( throw GridaException(ActivateImageAlreadyExists) } } + + @Transactional(readOnly = true) + fun validateRemainingAttemptCount(diaryId: Long) { + val generatedImagesCount = diaryImageReader.countGeneratedImages(diaryId) + if (generatedImagesCount >= DiaryImage.IMAGE_GENERATE_MAX_ATTEMPT_COUNT) { + throw GridaException(ImageGenerateAttemptOver) + } + } } diff --git a/grida-core/core-domain/src/main/kotlin/org/grida/error/CoreDomainErrorType.kt b/grida-core/core-domain/src/main/kotlin/org/grida/error/CoreDomainErrorType.kt index c4e930a..99310bd 100644 --- a/grida-core/core-domain/src/main/kotlin/org/grida/error/CoreDomainErrorType.kt +++ b/grida-core/core-domain/src/main/kotlin/org/grida/error/CoreDomainErrorType.kt @@ -33,6 +33,13 @@ data object ActivateImageAlreadyExists : CoreDomainErrorType { override val logLevel: LogLevel = INFO } +data object ImageGenerateAttemptOver : CoreDomainErrorType { + override val httpStatusCode: Int = BAD_REQUEST + override val errorCode: String = "IMAGE_400_2" + override val message: String = "이미지 생성 시도 횟수를 초과하였습니다." + override val logLevel: LogLevel = INFO +} + data object PasswordConfirmNotMatched : CoreDomainErrorType { override val httpStatusCode: Int = BAD_REQUEST override val errorCode: String = "AUTH_400_1" diff --git a/grida-database/database-rds/src/main/kotlin/org/grida/persistence/diaryimage/DiaryImageEntityRepository.kt b/grida-database/database-rds/src/main/kotlin/org/grida/persistence/diaryimage/DiaryImageEntityRepository.kt index fdd46cb..2863d98 100644 --- a/grida-database/database-rds/src/main/kotlin/org/grida/persistence/diaryimage/DiaryImageEntityRepository.kt +++ b/grida-database/database-rds/src/main/kotlin/org/grida/persistence/diaryimage/DiaryImageEntityRepository.kt @@ -30,6 +30,10 @@ class DiaryImageEntityRepository( return diaryImageEntity.toDomain() } + override fun countByDiaryId(diaryId: Long): Long { + return diaryImageJpaEntityRepository.countByDiaryId(diaryId) + } + override fun existsByDiaryIdAndStatus(diaryId: Long, status: ImageStatus): Boolean { return diaryImageJpaEntityRepository.existsByDiaryIdAndStatus(diaryId, status) } diff --git a/grida-database/database-rds/src/main/kotlin/org/grida/persistence/diaryimage/DiaryImageJpaEntityRepository.kt b/grida-database/database-rds/src/main/kotlin/org/grida/persistence/diaryimage/DiaryImageJpaEntityRepository.kt index 0cb3492..187debd 100644 --- a/grida-database/database-rds/src/main/kotlin/org/grida/persistence/diaryimage/DiaryImageJpaEntityRepository.kt +++ b/grida-database/database-rds/src/main/kotlin/org/grida/persistence/diaryimage/DiaryImageJpaEntityRepository.kt @@ -13,5 +13,7 @@ fun DiaryImageJpaEntityRepository.findByIdOrException( interface DiaryImageJpaEntityRepository : JpaRepository { + fun countByDiaryId(diaryId: Long): Long + fun existsByDiaryIdAndStatus(diaryId: Long, status: ImageStatus): Boolean } \ No newline at end of file From bffbc03294feec570cef08c6717a5d2697f1c0d1 Mon Sep 17 00:00:00 2001 From: wwan13 Date: Fri, 2 Aug 2024 00:30:50 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat=20:=20=EC=9D=BC=EA=B8=B0=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=83=9D=EC=84=B1=20=EA=B0=80=EB=8A=A5=20?= =?UTF-8?q?=ED=9A=9F=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../grida/presentation/v1/diary/DiaryController.kt | 7 +++++-- .../grida/presentation/v1/diary/dto/DiaryResponse.kt | 11 ++++++++--- .../kotlin/org/grida/domain/diaryimage/DiaryImage.kt | 2 +- .../org/grida/domain/diaryimage/DiaryImageService.kt | 8 +++++++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/grida-core/core-api/src/main/kotlin/org/grida/presentation/v1/diary/DiaryController.kt b/grida-core/core-api/src/main/kotlin/org/grida/presentation/v1/diary/DiaryController.kt index a43b66d..089f42c 100644 --- a/grida-core/core-api/src/main/kotlin/org/grida/presentation/v1/diary/DiaryController.kt +++ b/grida-core/core-api/src/main/kotlin/org/grida/presentation/v1/diary/DiaryController.kt @@ -5,6 +5,7 @@ import org.grida.api.ApiResponse import org.grida.api.dto.IdResponse import org.grida.domain.diary.DiaryScope import org.grida.domain.diary.DiaryService +import org.grida.domain.diaryimage.DiaryImageService import org.grida.presentation.v1.diary.dto.DiaryModifyRequest import org.grida.presentation.v1.diary.dto.DiaryRequest import org.grida.presentation.v1.diary.dto.DiaryResponse @@ -20,7 +21,8 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/v1/diary") class DiaryController( - private val diaryService: DiaryService + private val diaryService: DiaryService, + private val diaryImageService: DiaryImageService ) { @PostMapping @@ -39,7 +41,8 @@ class DiaryController( @PathVariable diaryId: Long ): ApiResponse { val diary = diaryService.readDiary(diaryId, userId) - val response = DiaryResponse.from(diary) + val remainCount = diaryImageService.countRemainImageGenerateAttempt(diaryId) + val response = DiaryResponse.from(diary, remainCount) return ApiResponse.success(response) } diff --git a/grida-core/core-api/src/main/kotlin/org/grida/presentation/v1/diary/dto/DiaryResponse.kt b/grida-core/core-api/src/main/kotlin/org/grida/presentation/v1/diary/dto/DiaryResponse.kt index 272b58b..77bf078 100644 --- a/grida-core/core-api/src/main/kotlin/org/grida/presentation/v1/diary/dto/DiaryResponse.kt +++ b/grida-core/core-api/src/main/kotlin/org/grida/presentation/v1/diary/dto/DiaryResponse.kt @@ -7,16 +7,21 @@ data class DiaryResponse( val content: String, val targetDate: String, val scope: String, - val createdAt: String + val createdAt: String, + val remainAttempt: Long ) { companion object { - fun from(diary: Diary): DiaryResponse { + fun from( + diary: Diary, + remainAttempt: Long + ): DiaryResponse { return DiaryResponse( content = diary.content, targetDate = DateTimeUtil.toDefaultDateFormat(diary.targetDate), scope = diary.scope.name, - createdAt = DateTimeUtil.toDefaultDateTimeFormat(diary.timestamp.createdAt) + createdAt = DateTimeUtil.toDefaultDateTimeFormat(diary.timestamp.createdAt), + remainAttempt = remainAttempt ) } } diff --git a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImage.kt b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImage.kt index dd40b9f..3c2d5f2 100644 --- a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImage.kt +++ b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImage.kt @@ -15,6 +15,6 @@ data class DiaryImage( } companion object { - const val IMAGE_GENERATE_MAX_ATTEMPT_COUNT = 3 + const val IMAGE_GENERATE_MAX_ATTEMPT_COUNT = 3L } } diff --git a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageService.kt b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageService.kt index 440fc76..ee8979a 100644 --- a/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageService.kt +++ b/grida-core/core-domain/src/main/kotlin/org/grida/domain/diaryimage/DiaryImageService.kt @@ -9,9 +9,10 @@ import org.springframework.stereotype.Service @Service class DiaryImageService( private val diaryImageAppender: DiaryImageAppender, - private val diaryImageGenerator: DiaryImageGenerator, + private val diaryImageReader: DiaryImageReader, private val diaryImageModifier: DiaryImageModifier, private val diaryImageValidator: DiaryImageValidator, + private val diaryImageGenerator: DiaryImageGenerator, private val diaryReader: DiaryReader, private val accessManager: AccessManager ) { @@ -35,6 +36,11 @@ class DiaryImageService( return diaryImageAppender.append(diaryImage, diaryId, userId) } + fun countRemainImageGenerateAttempt(diaryId: Long): Long { + val generatedImagesCount = diaryImageReader.countGeneratedImages(diaryId) + return DiaryImage.IMAGE_GENERATE_MAX_ATTEMPT_COUNT - generatedImagesCount + } + fun applyDiaryImage( diaryImageId: Long, diaryId: Long, From bd0694155aa243a9d50dd8e69c6d171fdb1feccb Mon Sep 17 00:00:00 2001 From: wwan13 Date: Fri, 2 Aug 2024 00:30:56 +0900 Subject: [PATCH 3/3] =?UTF-8?q?docs=20:=20=EC=9D=BC=EA=B8=B0=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=83=9D=EC=84=B1=20=EA=B0=80=EB=8A=A5=20?= =?UTF-8?q?=ED=9A=9F=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/kotlin/org/grida/docs/diary/DiaryApiDocsTest.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/grida-core/core-api/src/test/kotlin/org/grida/docs/diary/DiaryApiDocsTest.kt b/grida-core/core-api/src/test/kotlin/org/grida/docs/diary/DiaryApiDocsTest.kt index 63de211..295b43f 100644 --- a/grida-core/core-api/src/test/kotlin/org/grida/docs/diary/DiaryApiDocsTest.kt +++ b/grida-core/core-api/src/test/kotlin/org/grida/docs/diary/DiaryApiDocsTest.kt @@ -13,6 +13,7 @@ import org.grida.docs.ApiDocsTest import org.grida.domain.diary.Diary import org.grida.domain.diary.DiaryScope import org.grida.domain.diary.DiaryService +import org.grida.domain.diaryimage.DiaryImageService import org.grida.presentation.v1.diary.DiaryController import org.grida.presentation.v1.diary.dto.DiaryModifyRequest import org.grida.presentation.v1.diary.dto.DiaryRequest @@ -32,6 +33,9 @@ class DiaryApiDocsTest( @MockkBean private lateinit var diaryService: DiaryService + @MockkBean + private lateinit var diaryImageService: DiaryImageService + @Test fun `일기 생성 API`() { every { diaryService.appendDiary(any()) } returns 1L @@ -74,6 +78,7 @@ class DiaryApiDocsTest( ) every { diaryService.readDiary(any(), any()) } returns diary + every { diaryImageService.countRemainImageGenerateAttempt(any()) } returns 2L val api = api.get("/api/v1/diary/{diaryId}", 1L) { withBearerToken() @@ -89,6 +94,7 @@ class DiaryApiDocsTest( "data.targetDate" isTypeOf DATE whichMeans "대상 날짜", "data.scope" isTypeOf ENUM(DiaryScope::class) whichMeans "일기 공개 범위", "data.createdAt" isTypeOf DATETIME whichMeans "생성 시간", + "data.remainAttempt" isTypeOf NUMBER whichMeans "일기 이미지 재생성 가능 횟수", ) } }