Skip to content

Commit

Permalink
feat: 응원하기 기능 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
DongGeon0908 committed Dec 3, 2024
1 parent 44bddd3 commit 4927817
Show file tree
Hide file tree
Showing 17 changed files with 289 additions and 13 deletions.
14 changes: 14 additions & 0 deletions sql/cheer.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- 응원하기
CREATE TABLE `cheer_up`
(
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'cheer up id',
`uid` bigint NOT NULL COMMENT 'uid',
`target_uid` bigint NOT NULL COMMENT 'target_uid',
`cheered_at` date NOT NULL COMMENT '응원한 날짜',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '생성일',
`modified_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='응원하기';
CREATE UNIQUE INDEX uidx__uid__target_uid__cheered_at ON cheer_up (uid, target_uid, cheered_at);
CREATE INDEX idx__target ON cheer_up (target_uid);
CREATE INDEX idx__cheered_at__uid ON cheer_up (cheered_at, uid);
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.hero.alignlab.domain.cheer.application

import com.hero.alignlab.domain.auth.model.AuthUser
import com.hero.alignlab.domain.cheer.domain.CheerUp
import com.hero.alignlab.domain.cheer.model.request.CheerUpRequest
import com.hero.alignlab.domain.cheer.model.response.CheerUpResponse
import com.hero.alignlab.domain.group.application.GroupUserService
import com.hero.alignlab.ws.handler.ReactiveGroupUserWebSocketHandler
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.stereotype.Service
import java.time.LocalDate

@Service
class CheerUpFacade(
private val groupUserService: GroupUserService,
private val cheerUpService: CheerUpService,
private val reactiveGroupUserWebSocketHandler: ReactiveGroupUserWebSocketHandler,
) {
private val logger = KotlinLogging.logger { }

suspend fun cheerUp(user: AuthUser, request: CheerUpRequest): CheerUpResponse {
val now = LocalDate.now()

/** 동일 그룹 유저인지 확인하기 */
val groupUser = groupUserService.findByUidOrThrow(user.uid)
val otherGroupUsers = groupUserService.findAllByGroupId(groupUser.groupId)

val createdUids = otherGroupUsers.mapNotNull { otherGroupUser ->
val isSameGroup = request.uids.contains(otherGroupUser.uid)
val isExists = cheerUpService.existsByUidAndTargetUidAndCheeredAt(
uid = user.uid,
targetUid = otherGroupUser.uid,
cheeredAt = now
)

/** 동일 그룹, 응원하기를 진행하지 않는 경우에 action 진행 */
when (isSameGroup && !isExists) {
true -> {
runCatching {
cheerUpService.save(
CheerUp(
uid = user.uid,
targetUid = otherGroupUser.uid,
cheeredAt = now
)
)
}.onFailure { e ->
/** 이미 등록된 케이스인 경우가 대다수 */
logger.error(e) { "fail to create cheerUp" }
}.onSuccess { createdCheerUp ->
reactiveGroupUserWebSocketHandler.launchSendEventByCheerUp(
uid = createdCheerUp.targetUid,
groupId = otherGroupUser.groupId,
senderUid = user.uid,
)
}.getOrNull()?.targetUid
}

/** 응원하기를 진행하지 못한 경우 */
false -> null
}
}

return CheerUpResponse(createdUids.toSet())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.hero.alignlab.domain.cheer.application

import com.hero.alignlab.common.extension.executes
import com.hero.alignlab.config.database.TransactionTemplates
import com.hero.alignlab.domain.cheer.domain.CheerUp
import com.hero.alignlab.domain.cheer.infrastructure.CheerUpRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.springframework.stereotype.Service
import java.time.LocalDate

@Service
class CheerUpService(
private val cheerUpRepository: CheerUpRepository,
private val txTemplates: TransactionTemplates,
) {
suspend fun countAllByCheeredAtAndUid(cheeredAt: LocalDate, uid: Long): Long {
return withContext(Dispatchers.IO) {
cheerUpRepository.countAllByCheeredAtAndUid(cheeredAt, uid)
}
}

suspend fun existsByUidAndTargetUidAndCheeredAt(uid: Long, targetUid: Long, cheeredAt: LocalDate): Boolean {
return withContext(Dispatchers.IO) {
cheerUpRepository.existsByUidAndTargetUidAndCheeredAt(uid, targetUid, cheeredAt)
}
}

suspend fun save(cheerUp: CheerUp): CheerUp {
return txTemplates.writer.executes {
cheerUpRepository.save(cheerUp)
}
}

suspend fun findAllByTargetUidInAndCheeredAt(targetUids: Set<Long>, cheeredAt: LocalDate): List<CheerUp> {
return withContext(Dispatchers.IO) {
cheerUpRepository.findAllByTargetUidInAndCheeredAt(targetUids, cheeredAt)
}
}
}
22 changes: 22 additions & 0 deletions src/main/kotlin/com/hero/alignlab/domain/cheer/domain/CheerUp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.hero.alignlab.domain.cheer.domain

import com.hero.alignlab.domain.common.domain.BaseEntity
import jakarta.persistence.*
import java.time.LocalDate

@Entity
@Table(name = "cheer_up")
class CheerUp(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,

@Column(name = "uid")
val uid: Long,

@Column(name = "target_uid")
val targetUid: Long,

@Column(name = "cheered_at")
val cheeredAt: LocalDate,
) : BaseEntity()
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.hero.alignlab.domain.cheer.infrastructure

import com.hero.alignlab.domain.cheer.domain.CheerUp
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate

@Transactional(readOnly = true)
@Repository
interface CheerUpRepository : JpaRepository<CheerUp, Long> {
fun countAllByCheeredAtAndUid(cheeredAt: LocalDate, uid: Long): Long

fun existsByUidAndTargetUidAndCheeredAt(uid: Long, targetUid: Long, cheeredAt: LocalDate): Boolean

fun findAllByTargetUidInAndCheeredAt(targetUids: Set<Long>, cheeredAt: LocalDate): List<CheerUp>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.hero.alignlab.domain.cheer.model.request

data class CheerUpRequest(
/** 응원할 유저의 uids */
val uids: Set<Long>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.hero.alignlab.domain.cheer.model.response

data class CheerUpResponse(
/** 응원하기가 성공한 케이스에 대해 응답 */
val uids: Set<Long>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.hero.alignlab.domain.cheer.resource

import com.hero.alignlab.common.extension.wrapCreated
import com.hero.alignlab.common.model.Response
import com.hero.alignlab.domain.auth.model.AuthUser
import com.hero.alignlab.domain.cheer.application.CheerUpFacade
import com.hero.alignlab.domain.cheer.model.request.CheerUpRequest
import com.hero.alignlab.domain.cheer.model.response.CheerUpResponse
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@Tag(name = "응원하기 API")
@RestController
@RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
class CheerUpResource(
private val cheerUpFacade: CheerUpFacade,
) {
@Operation(summary = "응원하기")
@PostMapping("/cheer-up")
suspend fun cheerUp(
user: AuthUser,
@RequestBody request: CheerUpRequest,
): ResponseEntity<Response<CheerUpResponse>> {
return cheerUpFacade.cheerUp(user, request).wrapCreated()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.hero.alignlab.common.extension.executesOrNull
import com.hero.alignlab.common.model.HeroPageRequest
import com.hero.alignlab.config.database.TransactionTemplates
import com.hero.alignlab.domain.auth.model.AuthUser
import com.hero.alignlab.domain.cheer.application.CheerUpService
import com.hero.alignlab.domain.group.domain.Group
import com.hero.alignlab.domain.group.domain.GroupTag
import com.hero.alignlab.domain.group.domain.GroupUser
Expand All @@ -29,6 +30,7 @@ import kotlinx.coroutines.*
import org.springframework.context.ApplicationEventPublisher
import org.springframework.data.domain.Page
import org.springframework.stereotype.Service
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.concurrent.atomic.AtomicInteger

Expand All @@ -43,6 +45,7 @@ class GroupFacade(
private val publisher: ApplicationEventPublisher,
private val wsHandler: ReactiveGroupUserWebSocketHandler,
private val poseSnapshotService: PoseSnapshotService,
private val cheerUpService: CheerUpService,
) {
suspend fun createGroup(user: AuthUser, request: CreateGroupRequest): CreateGroupResponse {
return parZip(
Expand Down Expand Up @@ -216,10 +219,11 @@ class GroupFacade(
.take(5)
},
{ groupTagService.findByGroupId(groupId) },
) { group, groupUserScore, tags ->
{ cheerUpService.countAllByCheeredAtAndUid(LocalDate.now(), user.uid) },
) { group, groupUserScore, tags, countCheeredUp ->
val ownerGroupUser = userInfoService.getUserByIdOrThrow(group.ownerUid)

GetGroupResponse.of(group, tags, ownerGroupUser.nickname).run {
GetGroupResponse.of(group, tags, ownerGroupUser.nickname, countCheeredUp).run {
when (group.ownerUid == user.uid) {
true -> this
false -> this.copy(joinCode = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ class GroupUserService(
groupUserRepository.deleteById(groupUserId)
}

suspend fun findByUidOrThrow(uid: Long): GroupUser {
return findByUidOrNull(uid) ?: throw NotFoundException(ErrorCode.NOT_FOUND_GROUP_USER_ERROR)
}

suspend fun findByUidOrNull(uid: Long): GroupUser? {
return withContext(Dispatchers.IO) {
groupUserRepository.findByUid(uid)
Expand All @@ -133,4 +137,10 @@ class GroupUserService(
groupUserRepository.findByUidAndGroupIdIn(uid, groupIds)
}
}

suspend fun findAllByGroupId(groupId: Long): List<GroupUser> {
return withContext(Dispatchers.IO) {
groupUserRepository.findAllByGroupId(groupId)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ import org.springframework.data.domain.Pageable

interface GroupQRepository {
fun findByKeywordAndPage(keyword: String?, pageable: Pageable): Page<Group>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ class GroupQRepositoryImpl(
}
return if (order.isDescending) orderSpecifier.desc() else orderSpecifier.asc()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ interface GroupUserRepository : JpaRepository<GroupUser, Long> {
fun findByUidAndGroupIdIn(uid: Long, groupIds: List<Long>): List<GroupUser>

fun deleteAllByUid(uid: Long)

fun findAllByGroupId(groupId: Long): List<GroupUser>
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ data class GetGroupResponse(
val ranks: List<GetGroupRankResponse>? = null,
/** 그룹 태그 리스트 */
val tags: List<GroupTagResponse>?,
val countCheeredUp: Long? = null,
) {
companion object {
fun of(group: Group, tags: List<GroupTag>?, ownerName: String): GetGroupResponse {
fun of(
group: Group,
tags: List<GroupTag>?,
ownerName: String,
countCheeredUp: Long?,
): GetGroupResponse {
return GetGroupResponse(
id = group.id,
name = group.name,
Expand All @@ -34,6 +40,7 @@ data class GetGroupResponse(
userCount = group.userCount,
userCapacity = group.userCapacity,
tags = tags?.map { GroupTagResponse(it.id, it.name) },
countCheeredUp = countCheeredUp,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@ class ReactiveGroupUserWebSocketHandler(
}
}

fun launchSendEventByCheerUp(uid: Long, groupId: Long, senderUid: Long) {
groupUserByMap[groupId]?.let { groupUsers ->
CoroutineScope(Dispatchers.IO + Job()).launch {
val eventMessage = groupUsers.keys
.toList()
.let { uids -> groupUserWsFacade.generateEventMessage(uid, groupId, uids) }

groupUsers[uid]?.let { session ->
session
.send(Mono.just(session.textMessage(eventMessage.message())))
.subscribe()
}
}
}
}

/** 발송되는 순서가 중요하지 않다. */
private fun launchSendEvent(
uid: Long,
Expand Down
Loading

0 comments on commit 4927817

Please sign in to comment.