Skip to content

Commit

Permalink
[Feature/445] 보틀 조회 V2 API 구현한다 (#452)
Browse files Browse the repository at this point in the history
* feat: 보틀 조회 v2 api 구현

* feat: 스크립트 수정
  • Loading branch information
miseongk authored Sep 26, 2024
1 parent 4d06c55 commit 2b502f6
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 32 deletions.
20 changes: 14 additions & 6 deletions .github/workflows/daily-scrum-notification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@ jobs:
DATA: |
{
"channel": "C07LHAWLVNG",
"text": "<@U07L8AX9B4N><@U07L87A3WKY><@U07LC3Q1MEH><@U07LHEEU2BW><@U07LESGBQEP><@U07LMP4PY0L><@U07LHAXGYBE><@U07L87GGHJS>
**:calendar: 보틀즈 데일리 스크럼 :calendar:**\n\n
:one: - 완료된 작업 내용\n\n
:two: - 오늘 해야 할 작업\n\n
:three: - 겪고 있는 문제나 도움이 필요한 사항\n\n
오늘의 TMI나 아무말도 좋아:blob_aww: \n\n",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<@U07L8AX9B4N><@U07L87A3WKY><@U07LC3Q1MEH><@U07LHEEU2BW><@U07LESGBQEP><@U07LMP4PY0L><@U07LHAXGYBE><@U07L87GGHJS>\n\n
*:calendar: 보틀즈 데일리 스크럼 :calendar:*\n\n
:one: - 완료된 작업 내용\n\n
:two: - 오늘 해야 할 작업\n\n
:three: - 겪고 있는 문제나 도움이 필요한 사항\n\n
오늘의 TMI나 아무말도 좋아 :blob_aww: \n\n"
}
}
]
}
run: |
curl -X POST -H "Content-Type: application/json" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import com.nexters.bottles.api.bottle.facade.dto.RegisterLetterRequest
import com.nexters.bottles.api.global.interceptor.AuthRequired
import com.nexters.bottles.api.global.resolver.AuthUserId
import io.swagger.annotations.ApiOperation
import mu.KotlinLogging
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
Expand All @@ -25,9 +24,7 @@ import org.springframework.web.bind.annotation.RestController
class BottleController(
private val bottleFacade: BottleFacade
) {

private val log = KotlinLogging.logger {}


@ApiOperation("홈 - 받은 보틀 목록 조회하기")
@GetMapping
@AuthRequired
Expand Down Expand Up @@ -60,7 +57,7 @@ class BottleController(
bottleFacade.refuseBottle(userId, bottleId)
}

@ApiOperation("보틀 보관함 - 보틀 보관함 조회하기")
@ApiOperation("문답 - 핑퐁중인 보틀 조회하기")
@GetMapping("/ping-pong")
@AuthRequired
fun getPingPongList(@AuthUserId userId: Long): PingPongListResponse {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.nexters.bottles.api.bottle.controller

import com.nexters.bottles.api.bottle.facade.BottleFacadeV2
import com.nexters.bottles.api.bottle.facade.dto.RandomBottleListResponse
import com.nexters.bottles.api.bottle.facade.dto.SentBottleListResponse
import com.nexters.bottles.api.global.interceptor.AuthRequired
import com.nexters.bottles.api.global.resolver.AuthUserId
import io.swagger.annotations.ApiOperation
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/v2/bottles")
class BottleControllerV2(
private val bottleFacadeV2: BottleFacadeV2
) {

@ApiOperation("모래사장 - 랜덤으로 받은 보틀 목록 조회하기")
@GetMapping("/random")
@AuthRequired
fun getRandomBottlesList(@AuthUserId userId: Long): RandomBottleListResponse {
return bottleFacadeV2.getRandomBottles(userId)
}

@ApiOperation("호감 - 호감을 받은 보틀 목록 조회하기")
@GetMapping("/sent")
@AuthRequired
fun getSentBottlesList(@AuthUserId userId: Long): SentBottleListResponse {
return bottleFacadeV2.getSentBottles(userId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ class BottleFacade(

fun getNewBottles(userId: Long): BottleListResponse {
val user = userService.findByIdAndNotDeleted(userId)
val blockUserIds = blockContactListService.findAllByUserId(userId).map{ it.userId }.toSet() // 내가 차단한 유저
val blockedMeUserIds = blockContactListService.findAllByPhoneNumber(user.phoneNumber ?: throw IllegalStateException("핸드폰 번호를 등록해주세요"))
.map{ it.userId }.toSet() // 나를 차단한 유저
val blockUserIds = blockContactListService.findAllByUserId(userId).map { it.userId }.toSet() // 내가 차단한 유저
val blockedMeUserIds = blockContactListService.findAllByPhoneNumber(
user.phoneNumber ?: throw IllegalStateException("핸드폰 번호를 등록해주세요")
).map { it.userId }.toSet() // 나를 차단한 유저

if (isActiveMatching) {
val matchingHour = 18
Expand Down Expand Up @@ -98,7 +99,6 @@ class BottleFacade(
randomBottles = randomBottles,
sentBottles = sentBottles,
nextBottleLeftHours = getNextBottleLeftHours(LocalDateTime.now())

).also {
applicationEventPublisher.publishEvent(
UserApplicationEventDto(
Expand Down Expand Up @@ -128,7 +128,10 @@ class BottleFacade(
keyword = bottle.sourceUser.userProfile?.profileSelect?.keyword,
userImageUrl = bottle.sourceUser.userProfile?.blurredImageUrl,
expiredAt = bottle.expiredAt,
lastActivatedAt = getLastActivatedAtInKorean(basedAt = bottle.sourceUser.lastActivatedAt, now = LocalDateTime.now())
lastActivatedAt = getLastActivatedAtInKorean(
basedAt = bottle.sourceUser.lastActivatedAt,
now = LocalDateTime.now()
)
)
}

Expand Down Expand Up @@ -186,25 +189,26 @@ class BottleFacade(
val reportUserIds = userReportService.getReportRespondentList(userId)
.map { it.respondentUserId }
.toSet()
val blockUserIds = blockContactListService.findAllByUserId(userId).map{ it.userId }.toSet() // 내가 차단한 유저
val blockMeUserIds = blockContactListService.findAllByPhoneNumber(user.phoneNumber ?: throw IllegalStateException("핸드폰 번호를 등록해주세요"))
.map{ it.userId }.toSet() // 나를 차단한 유저
val blockUserIds = blockContactListService.findAllByUserId(userId).map { it.userId }.toSet() // 내가 차단한 유저
val blockMeUserIds = blockContactListService.findAllByPhoneNumber(
user.phoneNumber ?: throw IllegalStateException("핸드폰 번호를 등록해주세요")
).map { it.userId }.toSet() // 나를 차단한 유저

val activeBottles = groupByStatus[PingPongStatus.ACTIVE]
?.map { toPingPongBottleDto(it, user) }
?.filter { it.userId !in reportUserIds }
?.filter { it.userId !in blockUserIds}
?.filter { it.userId !in blockUserIds }
?.filter { it.userId !in blockMeUserIds }
?: emptyList()
val doneBottles =
(groupByStatus[PingPongStatus.STOPPED]
.orEmpty()
.filter { it.isNotExpiredAfterStopped(LocalDateTime.now()) } +
groupByStatus[PingPongStatus.MATCHED].orEmpty()
)
groupByStatus[PingPongStatus.MATCHED].orEmpty()
)
.map { toPingPongBottleDto(it, user) }
.filter { it.userId !in reportUserIds }
.filter { it.userId !in blockUserIds}
.filter { it.userId !in blockUserIds }
.filter { it.userId !in blockMeUserIds }
return PingPongListResponse(activeBottles = activeBottles, doneBottles = doneBottles)
}
Expand All @@ -222,7 +226,10 @@ class BottleFacade(
mbti = otherUser.userProfile?.profileSelect?.mbti,
keyword = otherUser.userProfile?.profileSelect?.keyword,
userImageUrl = otherUser.userProfile?.blurredImageUrl,
lastActivatedAt = getLastActivatedAtInKorean(basedAt = otherUser.lastActivatedAt, now = LocalDateTime.now()),
lastActivatedAt = getLastActivatedAtInKorean(
basedAt = otherUser.lastActivatedAt,
now = LocalDateTime.now()
),
)
}

Expand Down Expand Up @@ -299,7 +306,12 @@ class BottleFacade(
meetingPlaceImageUrl = null,
)
).also {
applicationEventPublisher.publishEvent(UserApplicationEventDto(userId = userId, basedAt = LocalDateTime.now()))
applicationEventPublisher.publishEvent(
UserApplicationEventDto(
userId = userId,
basedAt = LocalDateTime.now()
)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.nexters.bottles.api.bottle.facade

import com.nexters.bottles.api.bottle.event.dto.BottleMatchEventDto
import com.nexters.bottles.api.bottle.facade.dto.RandomBottleDto
import com.nexters.bottles.api.bottle.facade.dto.RandomBottleListResponse
import com.nexters.bottles.api.bottle.facade.dto.SentBottleDto
import com.nexters.bottles.api.bottle.facade.dto.SentBottleListResponse
import com.nexters.bottles.api.bottle.util.getLastActivatedAtInKorean
import com.nexters.bottles.api.user.component.event.dto.UserApplicationEventDto
import com.nexters.bottles.app.bottle.domain.Bottle
import com.nexters.bottles.app.bottle.domain.enum.BottleStatus
import com.nexters.bottles.app.bottle.service.BottleService
import com.nexters.bottles.app.user.domain.User
import com.nexters.bottles.app.user.service.BlockContactListService
import com.nexters.bottles.app.user.service.UserReportService
import com.nexters.bottles.app.user.service.UserService
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Component
import java.time.LocalDateTime
import java.time.LocalTime

@Component
class BottleFacadeV2(
private val bottleService: BottleService,
private val userService: UserService,
private val userReportService: UserReportService,
private val blockContactListService: BlockContactListService,
private val applicationEventPublisher: ApplicationEventPublisher,
) {

fun getRandomBottles(userId: Long): RandomBottleListResponse {
val user = userService.findByIdAndNotDeleted(userId)
val blockUserIds = blockContactListService.findAllByUserId(userId).map { it.userId }.toSet() // 내가 차단한 유저
val blockedMeUserIds = blockContactListService.findAllByPhoneNumber(
user.phoneNumber ?: throw IllegalStateException("핸드폰 번호를 등록해주세요")
).map { it.userId }.toSet() // 나를 차단한 유저

val matchingHour = 18
bottleService.matchRandomBottle(user, matchingHour, blockUserIds, blockedMeUserIds)
?.also {
applicationEventPublisher.publishEvent(
BottleMatchEventDto(
sourceUserId = it.sourceUser.id,
targetUserId = it.targetUser.id,
)
)
}

val bottles = bottleService.getNewBottlesByBottleStatus(user, setOf(BottleStatus.RANDOM))
val randomBottles = bottles.map { toRandomBottleDto(it, userId) }

return RandomBottleListResponse(
randomBottles = randomBottles,
nextBottleLeftHours = getNextBottleLeftHours(LocalDateTime.now())
).also {
publishUserApplicationEvent(user)
}
}

private fun getNextBottleLeftHours(now: LocalDateTime): Int {
return if (now.toLocalTime() > LocalTime.of(18, 0)) {
18 + (LocalTime.MAX.hour - now.hour)
} else {
LocalTime.of(18, 0).hour - now.hour
}
}

private fun toRandomBottleDto(bottle: Bottle, userId: Long): RandomBottleDto {
return RandomBottleDto(
id = bottle.id,
userId = bottle.findOtherUserId(userId = userId),
userName = bottle.sourceUser.getMaskedName(),
age = bottle.sourceUser.getKoreanAge(),
mbti = bottle.sourceUser.userProfile?.profileSelect?.mbti,
keyword = bottle.sourceUser.userProfile?.profileSelect?.keyword,
userImageUrl = bottle.sourceUser.userProfile?.blurredImageUrl,
expiredAt = bottle.expiredAt,
lastActivatedAt = getLastActivatedAtInKorean(
basedAt = bottle.sourceUser.lastActivatedAt,
now = LocalDateTime.now()
)
)
}

fun getSentBottles(userId: Long): SentBottleListResponse {
val user = userService.findByIdAndNotDeleted(userId)
val bottles = bottleService.getNewBottlesByBottleStatus(user, setOf(BottleStatus.SENT))

if (bottles.isEmpty()) {
return SentBottleListResponse(
sentBottles = emptyList()
).also {
publishUserApplicationEvent(user)
}
}

val blockUserIds = blockContactListService.findAllByUserId(userId).map { it.userId }.toSet() // 내가 차단한 유저
val blockedMeUserIds = blockContactListService.findAllByPhoneNumber(
user.phoneNumber ?: throw IllegalStateException("핸드폰 번호를 등록해주세요")
).map { it.userId }.toSet() // 나를 차단한 유저
val reportUserIds = userReportService.getReportRespondentList(userId)
.map { it.respondentUserId }
.toSet()

val sentBottles = bottles.map { toSentBottleDto(it, userId) }
.filter { it.userId !in reportUserIds }
.filter { it.userId !in blockUserIds }
.filter { it.userId !in blockedMeUserIds }

return SentBottleListResponse(
sentBottles = sentBottles
).also {
publishUserApplicationEvent(user)
}
}

private fun toSentBottleDto(bottle: Bottle, userId: Long): SentBottleDto {
return SentBottleDto(
id = bottle.id,
userId = bottle.findOtherUserId(userId = userId),
userName = bottle.sourceUser.getMaskedName(),
age = bottle.sourceUser.getKoreanAge(),
mbti = bottle.sourceUser.userProfile?.profileSelect?.mbti,
keyword = bottle.sourceUser.userProfile?.profileSelect?.keyword,
userImageUrl = bottle.sourceUser.userProfile?.blurredImageUrl,
expiredAt = bottle.expiredAt,
lastActivatedAt = getLastActivatedAtInKorean(
basedAt = bottle.sourceUser.lastActivatedAt,
now = LocalDateTime.now()
)
)
}

private fun publishUserApplicationEvent(user: User) {
applicationEventPublisher.publishEvent(
UserApplicationEventDto(
userId = user.id,
basedAt = LocalDateTime.now(),
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.nexters.bottles.api.bottle.facade.dto

import com.fasterxml.jackson.annotation.JsonFormat
import java.time.LocalDateTime

data class RandomBottleListResponse(
val randomBottles: List<RandomBottleDto>,
val nextBottleLeftHours: Int,
)

data class RandomBottleDto(
val id: Long,
val userId: Long,
val userName: String?,
val age: Int,
val mbti: String?,
val keyword: List<String>?,
val userImageUrl: String?,
val lastActivatedAt: String?,

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
val expiredAt: LocalDateTime
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.nexters.bottles.api.bottle.facade.dto

import com.fasterxml.jackson.annotation.JsonFormat
import java.time.LocalDateTime

data class SentBottleListResponse(
val sentBottles: List<SentBottleDto>,
)

data class SentBottleDto(
val id: Long,
val userId: Long,
val userName: String?,
val age: Int,
val mbti: String?,
val keyword: List<String>?,
val userImageUrl: String?,
val lastActivatedAt: String?,

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
val expiredAt: LocalDateTime
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.nexters.bottles.api.user.component.event

import com.nexters.bottles.api.user.component.event.dto.UserApplicationEventDto
import com.nexters.bottles.app.user.service.UserService
import mu.KotlinLogging
import org.springframework.context.event.EventListener
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Component
Expand All @@ -11,9 +10,7 @@ import org.springframework.stereotype.Component
class UserApplicationEventListener(
private val userService: UserService,
) {

private val log = KotlinLogging.logger { }


@Async
@EventListener
fun handleCustomEvent(event: UserApplicationEventDto) {
Expand Down
Loading

0 comments on commit 2b502f6

Please sign in to comment.