Skip to content

Commit

Permalink
ClubQuest에 데이터 분석을 위한 필드 추가 (#344)
Browse files Browse the repository at this point in the history
* ClubQuest에 데이터 분석을 위한 필드 추가

* Fix build
  • Loading branch information
Zeniuus authored Jul 7, 2024
1 parent 8485ea1 commit e9060ef
Show file tree
Hide file tree
Showing 18 changed files with 175 additions and 5 deletions.
47 changes: 46 additions & 1 deletion api-admin/api-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,20 @@ paths:
properties:
questNamePrefix:
type: string
purposeType:
$ref: '#/components/schemas/ClubQuestPurposeTypeEnumDTO'
startAt:
$ref: '#/components/schemas/EpochMillisTimestamp'
endAt:
$ref: '#/components/schemas/EpochMillisTimestamp'
dryRunResults:
items:
$ref: '#/components/schemas/ClubQuestCreateDryRunResultItemDTO'
required:
- questNamePrefix
- purposeType
- startAt
- endAt
- dryRunResults
responses:
'200':
Expand Down Expand Up @@ -424,6 +433,16 @@ paths:
components:
# Reusable schemas (data models)
schemas:
EpochMillisTimestamp:
description: 특정 시각을 표현하기 위한 모델.
type: object
properties:
value:
type: integer
format: int64
required:
- value

LocationDTO:
description: 위치를 위경도로 표현하기 위한 모델.
type: object
Expand Down Expand Up @@ -460,6 +479,14 @@ components:
- questCenterLocation
- targetBuildings

ClubQuestPurposeTypeEnumDTO:
type: string
enum:
- CRUSHER_CLUB
- DAILY_CLUB
- COLLABO_CLUB
- ESG_PARTNERS

GetCursoredClubQuestSummariesResultDTO:
type: object
properties:
Expand All @@ -480,11 +507,20 @@ components:
type: string
name:
type: string
purposeType:
$ref: '#/components/schemas/ClubQuestPurposeTypeEnumDTO'
startAt:
$ref: '#/components/schemas/EpochMillisTimestamp'
endAt:
$ref: '#/components/schemas/EpochMillisTimestamp'
shortenedUrl:
type: string
required:
- id
- name
- purposeType
- startAt
- endAt

ClubQuestDTO:
description: 퀘스트.
Expand All @@ -493,15 +529,24 @@ components:
type: string
name:
type: string
purposeType:
$ref: '#/components/schemas/ClubQuestPurposeTypeEnumDTO'
startAt:
$ref: '#/components/schemas/EpochMillisTimestamp'
endAt:
$ref: '#/components/schemas/EpochMillisTimestamp'
buildings:
type: array
items:
$ref: '#/components/schemas/ClubQuestTargetBuildingDTO'
shortenedAdminUrl:
shortenedUrl:
type: string
required:
- id
- name
- purposeType
- startAt
- endAt
- buildings

ClubQuestTargetBuildingDTO:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package club.staircrusher.admin_api.converter

import club.staircrusher.admin_api.spec.dto.EpochMillisTimestamp
import java.time.Instant

fun Instant.toDTO() = EpochMillisTimestamp(value = toEpochMilli())
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import club.staircrusher.quest.application.port.out.persistence.ClubQuestTargetP
import club.staircrusher.quest.application.port.out.web.ClubQuestTargetPlacesSearcher
import club.staircrusher.quest.domain.model.ClubQuest
import club.staircrusher.quest.domain.model.ClubQuestCreateDryRunResultItem
import club.staircrusher.quest.domain.model.ClubQuestPurposeType
import club.staircrusher.quest.domain.model.DryRunnedClubQuestTargetBuilding
import club.staircrusher.quest.domain.model.DryRunnedClubQuestTargetPlace
import club.staircrusher.stdlib.clock.SccClock
Expand All @@ -19,6 +20,7 @@ import org.mockito.kotlin.stub
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
import java.time.Duration

@SpringBootTest(classes = [SccSpringITApplication::class], webEnvironment = SpringBootTest.WebEnvironment.NONE)
class CrossValidateClubQuestPlacesUseCaseTest {
Expand Down Expand Up @@ -49,8 +51,12 @@ class CrossValidateClubQuestPlacesUseCaseTest {
val place1 = dataGenerator.createBuildingAndPlace()
val building = place1.building
val place2 = dataGenerator.createPlace(building = building)
val now = SccClock.instant()
val clubQuest = ClubQuest.of(
name = "quest",
purposeType = ClubQuestPurposeType.CRUSHER_CLUB,
startAt = now,
endAt = now + Duration.ofDays(7),
dryRunResultItem = ClubQuestCreateDryRunResultItem(
questNamePostfix = "1",
questCenterLocation = building.location,
Expand All @@ -76,7 +82,7 @@ class CrossValidateClubQuestPlacesUseCaseTest {
),
),
),
createdAt = SccClock.instant(),
createdAt = now,
)
clubQuestRepository.save(clubQuest)
Triple(place1, place2, clubQuest)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import club.staircrusher.quest.application.port.out.web.ClubQuestTargetPlacesSea
import club.staircrusher.quest.application.port.out.web.UrlShorteningService
import club.staircrusher.quest.domain.model.ClubQuest
import club.staircrusher.quest.domain.model.ClubQuestCreateDryRunResultItem
import club.staircrusher.quest.domain.model.ClubQuestPurposeType
import club.staircrusher.quest.domain.model.DryRunnedClubQuestTargetBuilding
import club.staircrusher.quest.domain.model.DryRunnedClubQuestTargetPlace
import club.staircrusher.quest.util.HumanReadablePrefixGenerator
Expand All @@ -23,6 +24,7 @@ import kotlinx.coroutines.withContext
import mu.KotlinLogging
import java.time.Clock
import java.time.Duration
import java.time.Instant

@Component
class ClubQuestCreateAplService(
Expand Down Expand Up @@ -153,13 +155,19 @@ class ClubQuestCreateAplService(

fun createFromDryRunResult(
questNamePrefix: String,
purposeType: ClubQuestPurposeType,
startAt: Instant,
endAt: Instant,
dryRunResultItems: List<ClubQuestCreateDryRunResultItem>,
): List<ClubQuest> {
val quests = transactionManager.doInTransaction {
dryRunResultItems.mapIndexed { idx, dryRunResultItem ->
clubQuestRepository.save(
ClubQuest.of(
name = "$questNamePrefix - ${getQuestNamePostfix(idx)}",
purposeType = purposeType,
startAt = startAt,
endAt = endAt,
dryRunResultItem = dryRunResultItem,
createdAt = clock.instant(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package club.staircrusher.quest.application.port.`in`

import club.staircrusher.place.application.port.out.web.MapsService
import club.staircrusher.quest.domain.model.ClubQuest
import club.staircrusher.quest.domain.model.ClubQuestPurposeType
import club.staircrusher.stdlib.clock.SccClock
import club.staircrusher.stdlib.di.annotation.Component
import club.staircrusher.stdlib.persistence.TransactionManager
import kotlinx.coroutines.runBlocking
import java.time.Duration

@Component
class CreateAndNotifyDailyClubQuestUseCase(
Expand Down Expand Up @@ -42,8 +45,12 @@ class CreateAndNotifyDailyClubQuestUseCase(
}

return transactionManager.doInTransaction {
val now = SccClock.instant()
clubQuestCreateAplService.createFromDryRunResult(
"${requesterName}님을 위한 일상 퀘스트",
purposeType = ClubQuestPurposeType.DAILY_CLUB,
startAt = now,
endAt = now + CLUB_QUEST_EXPIRY_DURATION,
dryRunResultItems = dryRunResultItems,
)[0]
}.let {
Expand All @@ -56,5 +63,6 @@ class CreateAndNotifyDailyClubQuestUseCase(

companion object {
private const val CLUB_QUEST_REGION_RADIUS_METERS = 300
private val CLUB_QUEST_EXPIRY_DURATION = Duration.ofDays(14)!!
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import java.time.Instant
class ClubQuest(
val id: String,
val name: String,
val purposeType: ClubQuestPurposeType,
val startAt: Instant,
val endAt: Instant,
val questCenterLocation: Location,
targetBuildings: List<ClubQuestTargetBuilding>,
val createdAt: Instant,
Expand Down Expand Up @@ -51,13 +54,20 @@ class ClubQuest(
companion object {
fun of(
name: String,
purposeType: ClubQuestPurposeType,
startAt: Instant,
endAt: Instant,
dryRunResultItem: ClubQuestCreateDryRunResultItem,
createdAt: Instant,
): ClubQuest {
val id = EntityIdGenerator.generateRandom()
require(startAt < endAt) { "퀘스트 종료 시각($endAt)은 퀘스트 시작 시각($startAt) 이후여야 합니다." }
return ClubQuest(
id = id,
name = name,
purposeType = purposeType,
startAt = startAt,
endAt = endAt,
questCenterLocation = dryRunResultItem.questCenterLocation,
targetBuildings = dryRunResultItem.targetBuildings.map {
ClubQuestTargetBuilding.of(valueObject = it, clubQuestId = id)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package club.staircrusher.quest.domain.model

enum class ClubQuestPurposeType {
CRUSHER_CLUB,
DAILY_CLUB,
COLLABO_CLUB,
ESG_PARTNERS,
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import java.time.Instant
data class ClubQuestSummary(
val id: String,
val name: String,
val purposeType: ClubQuestPurposeType,
val startAt: Instant,
val endAt: Instant,
val shortenedUrl: String?,
val createdAt: Instant,
)
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package club.staircrusher.quest.infra.adapter.`in`.controller

import club.staircrusher.admin_api.spec.dto.ClubQuestCreateDryRunResultItemDTO
import club.staircrusher.admin_api.spec.dto.ClubQuestPurposeTypeEnumDTO
import club.staircrusher.admin_api.spec.dto.ClubQuestTargetBuildingDTO
import club.staircrusher.admin_api.spec.dto.ClubQuestTargetPlaceDTO
import club.staircrusher.admin_api.spec.dto.CreateClubQuestRequest
import club.staircrusher.admin_api.spec.dto.EpochMillisTimestamp
import club.staircrusher.admin_api.spec.dto.LocationDTO
import club.staircrusher.quest.application.port.out.persistence.ClubQuestRepository
import club.staircrusher.quest.application.port.out.persistence.ClubQuestTargetBuildingRepository
import club.staircrusher.quest.application.port.out.persistence.ClubQuestTargetPlaceRepository
import club.staircrusher.stdlib.clock.SccClock
import club.staircrusher.testing.spring_it.base.SccSpringITBase
import org.springframework.beans.factory.annotation.Autowired
import java.time.Duration

open class ClubQuestITBase : SccSpringITBase() {
@Autowired
Expand All @@ -25,6 +29,9 @@ open class ClubQuestITBase : SccSpringITBase() {
placeId: String = "placeId"
) = CreateClubQuestRequest(
questNamePrefix = "test",
purposeType = ClubQuestPurposeTypeEnumDTO.CRUSHER_CLUB,
startAt = EpochMillisTimestamp(SccClock.instant().toEpochMilli()),
endAt = EpochMillisTimestamp((SccClock.instant() + Duration.ofDays(7)).toEpochMilli()),
dryRunResults = listOf(
ClubQuestCreateDryRunResultItemDTO(
questNamePostfix = "test",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import club.staircrusher.quest.application.port.`in`.GetCursoredClubQuestSummari
import club.staircrusher.quest.application.port.out.persistence.ClubQuestRepository
import club.staircrusher.quest.infra.adapter.`in`.converter.toDTO
import club.staircrusher.quest.infra.adapter.`in`.converter.toModel
import club.staircrusher.stdlib.time.epochMilliToInstant
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
Expand Down Expand Up @@ -88,6 +89,9 @@ class AdminClubQuestController(
fun createClubQuest(@RequestBody request: CreateClubQuestRequest): CreateClubQuestResponseDTO {
val quests = clubQuestCreateAplService.createFromDryRunResult(
questNamePrefix = request.questNamePrefix,
purposeType = request.purposeType.toModel(),
startAt = request.startAt.value.epochMilliToInstant(),
endAt = request.endAt.value.epochMilliToInstant(),
dryRunResultItems = request.dryRunResults.map { it.toModel() }
)
quests.forEach { quest ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import club.staircrusher.admin_api.converter.toModel
import club.staircrusher.admin_api.spec.dto.ClubQuestCreateDryRunResultItemDTO
import club.staircrusher.admin_api.spec.dto.ClubQuestCreateRegionTypeDTO
import club.staircrusher.admin_api.spec.dto.ClubQuestDTO
import club.staircrusher.admin_api.spec.dto.ClubQuestPurposeTypeEnumDTO
import club.staircrusher.admin_api.spec.dto.ClubQuestSummaryDTO
import club.staircrusher.admin_api.spec.dto.ClubQuestTargetBuildingDTO
import club.staircrusher.admin_api.spec.dto.ClubQuestTargetPlaceDTO
import club.staircrusher.place.domain.model.Place
import club.staircrusher.quest.application.port.`in`.ClubQuestCreateRegionType
import club.staircrusher.quest.application.port.`in`.ClubQuestWithDtoInfo
import club.staircrusher.quest.domain.model.ClubQuestCreateDryRunResultItem
import club.staircrusher.quest.domain.model.ClubQuestPurposeType
import club.staircrusher.quest.domain.model.ClubQuestSummary
import club.staircrusher.quest.domain.model.ClubQuestTargetBuilding
import club.staircrusher.quest.domain.model.ClubQuestTargetPlace
Expand All @@ -23,6 +25,20 @@ fun ClubQuestCreateRegionTypeDTO.toModel() = when (this) {
ClubQuestCreateRegionTypeDTO.POLYGON -> ClubQuestCreateRegionType.POLYGON
}

fun ClubQuestPurposeType.toDTO() = when (this) {
ClubQuestPurposeType.CRUSHER_CLUB -> ClubQuestPurposeTypeEnumDTO.CRUSHER_CLUB
ClubQuestPurposeType.DAILY_CLUB -> ClubQuestPurposeTypeEnumDTO.DAILY_CLUB
ClubQuestPurposeType.COLLABO_CLUB -> ClubQuestPurposeTypeEnumDTO.COLLABO_CLUB
ClubQuestPurposeType.ESG_PARTNERS -> ClubQuestPurposeTypeEnumDTO.ESG_PARTNERS
}

fun ClubQuestPurposeTypeEnumDTO.toModel() = when (this) {
ClubQuestPurposeTypeEnumDTO.CRUSHER_CLUB -> ClubQuestPurposeType.CRUSHER_CLUB
ClubQuestPurposeTypeEnumDTO.DAILY_CLUB -> ClubQuestPurposeType.DAILY_CLUB
ClubQuestPurposeTypeEnumDTO.COLLABO_CLUB -> ClubQuestPurposeType.COLLABO_CLUB
ClubQuestPurposeTypeEnumDTO.ESG_PARTNERS -> ClubQuestPurposeType.ESG_PARTNERS
}

fun ClubQuestCreateDryRunResultItemDTO.toModel() = ClubQuestCreateDryRunResultItem(
questNamePostfix = questNamePostfix,
questCenterLocation = questCenterLocation.toModel(),
Expand Down Expand Up @@ -63,8 +79,11 @@ fun DryRunnedClubQuestTargetPlace.toDTO(isConquered: Boolean): ClubQuestTargetPl
fun ClubQuestWithDtoInfo.toDTO() = ClubQuestDTO(
id = quest.id,
name = quest.name,
purposeType = quest.purposeType.toDTO(),
startAt = quest.startAt.toDTO(),
endAt = quest.endAt.toDTO(),
buildings = quest.targetBuildings.map { it.toDTO(conqueredPlaceIds, placeById) },
shortenedAdminUrl = quest.shortenedAdminUrl,
shortenedUrl = quest.shortenedAdminUrl,
)

fun ClubQuestTargetBuildingDTO.toModel() = DryRunnedClubQuestTargetBuilding(
Expand Down Expand Up @@ -117,5 +136,8 @@ fun ClubQuestTargetPlace.toDTO(
fun ClubQuestSummary.toDTO() = ClubQuestSummaryDTO(
id = id,
name = name,
purposeType = purposeType.toDTO(),
startAt = startAt.toDTO(),
endAt = endAt.toDTO(),
shortenedUrl = shortenedUrl,
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import club.staircrusher.stdlib.time.toOffsetDateTime
fun ClubQuest.toPersistenceModel() = Club_quest(
id = id,
name = name,
purpose_type = purposeType,
start_at = startAt.toOffsetDateTime(),
end_at = endAt.toOffsetDateTime(),
quest_center_location_x = questCenterLocation.lng,
quest_center_location_y = questCenterLocation.lat,
shortened_admin_url = shortenedAdminUrl,
Expand All @@ -22,6 +25,9 @@ fun ClubQuest.toPersistenceModel() = Club_quest(
fun Club_quest.toDomainModel(targetBuildings: List<ClubQuestTargetBuilding>) = ClubQuest(
id = id,
name = name,
purposeType = purpose_type,
startAt = start_at.toInstant(),
endAt = end_at.toInstant(),
questCenterLocation = Location(lng = quest_center_location_x, lat = quest_center_location_y),
targetBuildings = targetBuildings.sortedBy { it.name.padStart(5, '0') },
shortenedAdminUrl = shortened_admin_url,
Expand Down
Loading

0 comments on commit e9060ef

Please sign in to comment.