diff --git a/api/src/main/kotlin/com/mashup/dojo/AdminController.kt b/api/src/main/kotlin/com/mashup/dojo/AdminController.kt index feec5126..9ff11025 100644 --- a/api/src/main/kotlin/com/mashup/dojo/AdminController.kt +++ b/api/src/main/kotlin/com/mashup/dojo/AdminController.kt @@ -77,7 +77,7 @@ class AdminController( request.questionIds val customQuestionSet = questionUseCase.createCustomQuestionSet( - QuestionUseCase.CreateQuestionSetCommand(request.questionIds, request.publishedAt) + QuestionUseCase.CreateQuestionSetCommand(request.questionIds, request.publishedAt, request.endAt) ) return DojoApiResponse.success(customQuestionSet.id) diff --git a/api/src/main/kotlin/com/mashup/dojo/dto/QuestionDto.kt b/api/src/main/kotlin/com/mashup/dojo/dto/QuestionDto.kt index aaa9f017..dcad696c 100644 --- a/api/src/main/kotlin/com/mashup/dojo/dto/QuestionDto.kt +++ b/api/src/main/kotlin/com/mashup/dojo/dto/QuestionDto.kt @@ -39,6 +39,8 @@ data class QuestionSetCustomCreateRequest( val questionIds: List, @Schema(description = "질문지 세트를 발행할 시각") val publishedAt: LocalDateTime, + @Schema(description = "질문지 세트를 종료할 시각") + val endAt: LocalDateTime, ) @Schema(description = "질문지 (투표 용지) 한 다스 조회 응답") diff --git a/entity/src/main/kotlin/com/mashup/dojo/QuestionSetRepository.kt b/entity/src/main/kotlin/com/mashup/dojo/QuestionSetRepository.kt index 3ce7eba6..05c2b5ab 100644 --- a/entity/src/main/kotlin/com/mashup/dojo/QuestionSetRepository.kt +++ b/entity/src/main/kotlin/com/mashup/dojo/QuestionSetRepository.kt @@ -5,6 +5,8 @@ import jakarta.persistence.AttributeConverter import jakarta.persistence.Column import jakarta.persistence.Convert import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated import jakarta.persistence.Id import jakarta.persistence.Table import org.springframework.data.jpa.repository.JpaRepository @@ -12,10 +14,16 @@ import java.time.LocalDateTime interface QuestionSetRepository : JpaRepository { // publishedYn : True && publishedAt > now -> 현재 운영중인 QuestionSet - fun findFirstByPublishedYnTrueAndPublishedAtAfterOrderByPublishedAt(compareTime: LocalDateTime = LocalDateTime.now()): QuestionSetEntity? + fun findByPublishedAtAfterAndEndAtBefore( + publishedCompareTime: LocalDateTime = LocalDateTime.now(), + endTimeCompareTime: LocalDateTime = LocalDateTime.now(), + ): QuestionSetEntity? // publishedYn : True && publishedAt < now -> 발행 직전(예정) QuestionSet - fun findFirstByPublishedYnTrueAndPublishedAtBeforeOrderByPublishedAt(compareTime: LocalDateTime = LocalDateTime.now()): QuestionSetEntity? + fun findByStatusAndPublishedAtAfter( + status: Status, + compareTime: LocalDateTime = LocalDateTime.now(), + ): QuestionSetEntity? fun findTopByOrderByPublishedAtDesc(): QuestionSetEntity? } @@ -29,11 +37,20 @@ class QuestionSetEntity( @Column(name = "question_ids", nullable = false) val questionIds: List, @Column(name = "published_yn", nullable = false) - val publishedYn: Boolean = false, + @Enumerated(EnumType.STRING) + val status: Status, @Column(name = "published_at", nullable = false) val publishedAt: LocalDateTime, + @Column(name = "end_at", nullable = false) + val endAt: LocalDateTime, ) : BaseEntity() +enum class Status { + TERMINATED, // 종료 + ACTIVE, // 운영중 + UPCOMING, // 예정 +} + class QuestionIdConverter : AttributeConverter, String> { override fun convertToDatabaseColumn(attribute: List): String { return attribute.joinToString(DELIMITER) diff --git a/service/src/main/kotlin/com/mashup/dojo/domain/QuestionSet.kt b/service/src/main/kotlin/com/mashup/dojo/domain/QuestionSet.kt index 9378ce49..da77c99f 100644 --- a/service/src/main/kotlin/com/mashup/dojo/domain/QuestionSet.kt +++ b/service/src/main/kotlin/com/mashup/dojo/domain/QuestionSet.kt @@ -2,6 +2,7 @@ package com.mashup.dojo.domain import com.mashup.dojo.UUIDGenerator import java.time.LocalDateTime +import java.time.LocalTime @JvmInline value class QuestionSetId(val value: String) @@ -16,20 +17,34 @@ data class QuestionSet( // 1 based-order val questionIds: List, // 발행 여부 - val publishedYn: Boolean = false, + val status: PublishStatus = PublishStatus.UPCOMING, // 질문 발행일 val publishedAt: LocalDateTime, + val endAt: LocalDateTime, ) { companion object { fun create( questionOrders: List, publishedAt: LocalDateTime, + endAt: LocalDateTime, ): QuestionSet { return QuestionSet( id = QuestionSetId(UUIDGenerator.generate()), questionIds = questionOrders, - publishedAt = publishedAt + publishedAt = publishedAt, + endAt = endAt ) } } } + +enum class PublishStatus { + TERMINATED, // 종료 + ACTIVE, // 운영중 + UPCOMING, // 예정 +} + +object PublishedTime { + val OPEN_TIME_1: LocalTime = LocalTime.of(8, 0, 0) + val OPEN_TIME_2: LocalTime = LocalTime.of(11, 0, 0) +} diff --git a/service/src/main/kotlin/com/mashup/dojo/service/DefaultPickService.kt b/service/src/main/kotlin/com/mashup/dojo/service/DefaultPickService.kt index 5b5f9da9..99587089 100644 --- a/service/src/main/kotlin/com/mashup/dojo/service/DefaultPickService.kt +++ b/service/src/main/kotlin/com/mashup/dojo/service/DefaultPickService.kt @@ -106,7 +106,6 @@ class DefaultPickService( ): List { return pickRepository.findAllByPickedId(pickedMemberId.value) .map { it.toPick() } - // return listOf(DEFAULT_PICK) } override fun getSolvedPickList( diff --git a/service/src/main/kotlin/com/mashup/dojo/service/QuestionService.kt b/service/src/main/kotlin/com/mashup/dojo/service/QuestionService.kt index 04975ecd..2d12b003 100644 --- a/service/src/main/kotlin/com/mashup/dojo/service/QuestionService.kt +++ b/service/src/main/kotlin/com/mashup/dojo/service/QuestionService.kt @@ -8,9 +8,12 @@ import com.mashup.dojo.QuestionSetEntity import com.mashup.dojo.QuestionSetRepository import com.mashup.dojo.QuestionSheetEntity import com.mashup.dojo.QuestionSheetRepository +import com.mashup.dojo.Status import com.mashup.dojo.domain.ImageId import com.mashup.dojo.domain.Member import com.mashup.dojo.domain.MemberId +import com.mashup.dojo.domain.PublishStatus +import com.mashup.dojo.domain.PublishedTime import com.mashup.dojo.domain.Question import com.mashup.dojo.domain.QuestionCategory import com.mashup.dojo.domain.QuestionId @@ -26,7 +29,9 @@ import org.springframework.data.domain.Pageable import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate import java.time.LocalDateTime +import java.time.LocalTime import kotlin.math.floor private val log = KotlinLogging.logger {} @@ -58,14 +63,12 @@ interface QuestionService { emojiImageId: ImageId, ): QuestionId - fun createQuestionSet( - excludedQuestionSet: QuestionSet?, - publishedAt: LocalDateTime, - ): QuestionSetId + fun createQuestionSet(excludedQuestionSet: QuestionSet?): QuestionSetId fun createQuestionSet( questionIds: List, publishedAt: LocalDateTime, + endAt: LocalDateTime, ): QuestionSet fun createQuestionSheets( @@ -107,7 +110,7 @@ class DefaultQuestionService( // 현재 운영중인 QuestionSet override fun getOperatingQuestionSet(): QuestionSet? { - return questionSetRepository.findFirstByPublishedYnTrueAndPublishedAtAfterOrderByPublishedAt() + return questionSetRepository.findByPublishedAtAfterAndEndAtBefore(LocalDateTime.now(), LocalDateTime.now()) ?.toQuestionSet() ?: run { log.error { "Published And Operating QuestionSet Entity not found" } null @@ -116,7 +119,7 @@ class DefaultQuestionService( // 발행 출격 준비 완료 QuestionSet override fun getNextOperatingQuestionSet(): QuestionSet? { - return questionSetRepository.findFirstByPublishedYnTrueAndPublishedAtBeforeOrderByPublishedAt() + return questionSetRepository.findByStatusAndPublishedAtAfter(Status.UPCOMING, LocalDateTime.now()) ?.toQuestionSet() ?: run { log.error { "Published And Prepared for sortie QuestionSet Entity not found" } null @@ -140,10 +143,7 @@ class DefaultQuestionService( } @Transactional - override fun createQuestionSet( - excludedQuestionSet: QuestionSet?, - publishedAt: LocalDateTime, - ): QuestionSetId { + override fun createQuestionSet(excludedQuestionSet: QuestionSet?): QuestionSetId { // 비율에 따라 questionType 선정 val friendQuestionSize = floor(questionSetSize * friendQuestionRatio).toInt() val excludedQuestionIds: List = excludedQuestionSet?.questionIds?.map { it.questionId.value } ?: emptyList() @@ -172,13 +172,34 @@ class DefaultQuestionService( QuestionOrder(questionId = question.id, order = index) } + // 마지막 QSet 의 발행 시각 가져오기 + val latestQSet = getLatestPublishedQuestionSet() + + val publishedTime = + latestQSet?.endAt ?: run { + val now = LocalTime.now() + val today = LocalDate.now() + + when { + now.isBefore(PublishedTime.OPEN_TIME_1) -> today.atTime(PublishedTime.OPEN_TIME_1) + now.isBefore(PublishedTime.OPEN_TIME_2) -> today.atTime(PublishedTime.OPEN_TIME_2) + else -> today.plusDays(1).atTime(PublishedTime.OPEN_TIME_1) + } + } + + val endTime = + if (publishedTime.toLocalTime() == PublishedTime.OPEN_TIME_1) { + publishedTime.toLocalDate().atTime(PublishedTime.OPEN_TIME_2) + } else { // publishedTime.toLocalTime() == PublishedTime.OPEN_TIME_2 + publishedTime.toLocalDate().plusDays(1).atTime(PublishedTime.OPEN_TIME_1) + } + // 우선 만들어지는 시점이 다음 투표 이전에 만들어질 QSet 을 만든다고 가정, 따라서 해당 QSet 은 바로 다음 발행될 QSet - // todo: QSet에서 publishedAt 이 가장 큰 녀석 가져온 후 해당 publishedAt 보다 큰 startTime 을 가진 PickTime 정보 가져옴 - // (fix publishedAt) 현재 PickTime Entity 는 LocalTime 으로 저장되고 있음. LocalDateTime 이어야 위 가정이 유효 val questionSetEntity = QuestionSet.create( questionOrders = questionOrders, - publishedAt = publishedAt + publishedAt = publishedTime, + endAt = endTime ).toEntity() val id = questionSetRepository.save(questionSetEntity).id @@ -190,12 +211,13 @@ class DefaultQuestionService( override fun createQuestionSet( questionIds: List, publishedAt: LocalDateTime, + endAt: LocalDateTime, ): QuestionSet { require(questionIds.size == questionSetSize) { "questions size for QuestionSet must be $questionSetSize" } require(publishedAt >= LocalDateTime.now()) { "publishedAt must be in the future" } val questionOrders = questionIds.mapIndexed { idx, qId -> QuestionOrder(qId, idx + 1) } - val questionSet = QuestionSet.create(questionOrders, publishedAt) + val questionSet = QuestionSet.create(questionOrders, publishedAt, endAt) questionSetRepository.save(questionSet.toEntity()) return questionSet @@ -235,7 +257,7 @@ class DefaultQuestionService( emojiImageId = ImageId("345678") ) - val SAMPLE_QUESTION_SET = + private val SAMPLE_QUESTION_SET = QuestionSet( id = QuestionSetId("1"), questionIds = @@ -253,7 +275,8 @@ class DefaultQuestionService( QuestionOrder(QuestionId("11"), 11), QuestionOrder(QuestionId("12"), 12) ), - publishedAt = LocalDateTime.now() + publishedAt = LocalDateTime.now(), + endAt = LocalDateTime.now().plusHours(12) ) private val SAMPLE_QUESTION_SHEET = @@ -332,7 +355,9 @@ private fun QuestionSet.toEntity(): QuestionSetEntity { return QuestionSetEntity( id = id.value, questionIds = questionIds, - publishedAt = publishedAt + status = status.toDomainPublishStatus(), + publishedAt = publishedAt, + endAt = endAt ) } @@ -348,7 +373,9 @@ private fun QuestionSetEntity.toQuestionSet(): QuestionSet { return QuestionSet( id = QuestionSetId(id), questionIds = questionOrders, - publishedAt = publishedAt + status = status.toDomainPublishStatus(), + publishedAt = publishedAt, + endAt = endAt ) } @@ -361,3 +388,19 @@ private fun QuestionSheetEntity.toQuestionSheetWithCandidatesId(): QuestionSheet candidates = candidates.map { MemberId(it) }.toList() ) } + +private fun Status.toDomainPublishStatus(): PublishStatus { + return when (this) { + Status.TERMINATED -> PublishStatus.TERMINATED + Status.ACTIVE -> PublishStatus.ACTIVE + Status.UPCOMING -> PublishStatus.UPCOMING + } +} + +private fun PublishStatus.toDomainPublishStatus(): Status { + return when (this) { + PublishStatus.TERMINATED -> Status.TERMINATED + PublishStatus.ACTIVE -> Status.ACTIVE + PublishStatus.UPCOMING -> Status.UPCOMING + } +} diff --git a/service/src/main/kotlin/com/mashup/dojo/usecase/QuestionUseCase.kt b/service/src/main/kotlin/com/mashup/dojo/usecase/QuestionUseCase.kt index 679fbaf5..13c1cf13 100644 --- a/service/src/main/kotlin/com/mashup/dojo/usecase/QuestionUseCase.kt +++ b/service/src/main/kotlin/com/mashup/dojo/usecase/QuestionUseCase.kt @@ -34,6 +34,7 @@ interface QuestionUseCase { data class CreateQuestionSetCommand( val questionIdList: List, val publishedAt: LocalDateTime, + val endAt: LocalDateTime, ) data class GetQuestionSheetsResult( @@ -105,15 +106,14 @@ class DefaultQuestionUseCase( @Transactional override fun createQuestionSet(): QuestionSetId { // 가장 마지막에 만들어진 QSet 정보는 제외 - val currentQuestionSet = questionService.getLatestPublishedQuestionSet() - val nextPickTime = pickService.getNextPickTime() + val latestQSet = questionService.getLatestPublishedQuestionSet() - return questionService.createQuestionSet(excludedQuestionSet = currentQuestionSet, nextPickTime) + return questionService.createQuestionSet(excludedQuestionSet = latestQSet) } @Transactional override fun createCustomQuestionSet(command: QuestionUseCase.CreateQuestionSetCommand): QuestionSet { - return questionService.createQuestionSet(command.questionIdList, command.publishedAt) + return questionService.createQuestionSet(command.questionIdList, command.publishedAt, command.endAt) } @Transactional