Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DPMBE-73] 인터렉션 init, 비지니스 로직을 구현한다 #111

Merged
merged 14 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.depromeet.whatnow.api.interaction.controller

import com.depromeet.whatnow.api.interaction.dto.InteractionDetailResponse
import com.depromeet.whatnow.api.interaction.dto.InteractionResponse
import com.depromeet.whatnow.api.interaction.usecase.InteractionReadDetailUseCase
import com.depromeet.whatnow.api.interaction.usecase.InteractionReadUseCase
import com.depromeet.whatnow.api.interaction.usecase.InteractionSendUseCase
import com.depromeet.whatnow.domains.interaction.domain.InteractionType
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@Tag(name = "7. [인터렉션]")
@RequestMapping("/v1")
@SecurityRequirement(name = "access-token")
class InteractionController(
val interactionSendUseCase: InteractionSendUseCase,
val interactionReadUseCase: InteractionReadUseCase,
val interactionReadDetailUseCase: InteractionReadDetailUseCase,
) {
@PostMapping("/promises/{promiseId}/interactions/{interactionType}/target/{targetUserId}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

target 이 userId 와 매칭 되는 맥락이 부족한 것 같아요.
users/{user-id} ? 같은 느낌

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 뭔가 직관적인게 있을까요?!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰 남겨주시면 확인후 다음 PR에서 반영해보겠습니다

@Operation(summary = "인터렉션을 발송합니다.")
fun sendInteraction(
@PathVariable promiseId: Long,
@PathVariable interactionType: InteractionType,
@PathVariable targetUserId: Long,
): ResponseEntity<Unit> {
interactionSendUseCase.execute(promiseId, interactionType, targetUserId)
return ResponseEntity.status(HttpStatus.ACCEPTED).body(Unit)
}

@GetMapping("/users/me/promises/{promiseId}/interactions")
@Operation(summary = "자신의 인터렉션 정보를 가져옵니다.")
fun getMyInteraction(
@PathVariable promiseId: Long,
): InteractionResponse {
return interactionReadUseCase.findMyInteraction(promiseId)
}

@GetMapping("/users/me/promises/{promiseId}/interactions/{interactionType}")
@Operation(summary = "자신의 인터렉션 상세 정보를 가져옵니다.")
fun getMyInteractionDetail(
@PathVariable promiseId: Long,
@PathVariable interactionType: InteractionType,
): InteractionDetailResponse {
return interactionReadDetailUseCase.findMyInteractionDetail(promiseId, interactionType)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


매핑이
/promises/{promiseId}/interactions/{interactionType}
로 해도 충분할것 같고
다른 유저아이디로 조회할게 있다 하면
/promises/{promiseId}/user/{userId}/interactions/{interactionType}
이렇게 하는게 뭔가 더 직관적인것같아요

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 의견인것같슴니다!

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.depromeet.whatnow.api.interaction.dto

import com.depromeet.whatnow.common.vo.UserInfoVo
import com.depromeet.whatnow.domains.interaction.domain.InteractionType
import com.depromeet.whatnow.domains.interactionhistory.domain.InteractionHistory

data class InteractionDetailDto(
val senderUser: UserInfoVo,
val count: Long,
val interactionType: InteractionType,
) {
companion object {
fun from(it: InteractionHistory) {
TODO("Not yet implemented")
}
// fun from(interaction: Interaction): InteractionDetailDto {
// return InteractionDetailDto(interaction.promiseId, interaction.userId, interaction.interactionType, interaction.count)
// }
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요곤 아직 구현이 안된건가요?

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.depromeet.whatnow.api.interaction.dto

data class InteractionDetailResponse(
val interactions: List<InteractionDetailDto>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.depromeet.whatnow.api.interaction.dto

import com.depromeet.whatnow.domains.interaction.domain.Interaction
import com.depromeet.whatnow.domains.interaction.domain.InteractionType

data class InteractionDto(
val promiseId: Long,
val userId: Long,
val interactionType: InteractionType,
val count: Long,
) {
companion object {
fun from(interaction: Interaction): InteractionDto {
return InteractionDto(interaction.promiseId, interaction.userId, interaction.interactionType, interaction.count)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.depromeet.whatnow.api.interaction.dto

data class InteractionResponse(
val interactionDtoList: List<InteractionDto>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.depromeet.whatnow.api.interaction.usecase

import com.depromeet.whatnow.annotation.UseCase
import com.depromeet.whatnow.api.interaction.dto.InteractionDetailDto
import com.depromeet.whatnow.api.interaction.dto.InteractionDetailResponse
import com.depromeet.whatnow.config.security.SecurityUtils
import com.depromeet.whatnow.domains.interaction.domain.InteractionType
import com.depromeet.whatnow.domains.interactionhistory.service.InteractionHistoryDomainService
import com.depromeet.whatnow.domains.user.adapter.UserAdapter

@UseCase
class InteractionReadDetailUseCase(
val interactionHistoryDomainService: InteractionHistoryDomainService,
val userAdapter: UserAdapter,
) {
fun findMyInteractionDetail(promiseId: Long, interactionType: InteractionType): InteractionDetailResponse {
val userId = SecurityUtils.currentUserId
return InteractionDetailResponse(
interactionHistoryDomainService.queryAllByInteractionType(userId, promiseId, interactionType)
.groupBy { it.targetUserId }.map { (targetUserId, interactionHistories) ->
InteractionDetailDto(
userAdapter.queryUser(targetUserId).toUserInfoVo(),
interactionHistories.size.toLong(),
interactionType,
)
}.sortedByDescending { it.count },
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.depromeet.whatnow.api.interaction.usecase

import com.depromeet.whatnow.annotation.UseCase
import com.depromeet.whatnow.api.interaction.dto.InteractionDto
import com.depromeet.whatnow.api.interaction.dto.InteractionResponse
import com.depromeet.whatnow.config.security.SecurityUtils
import com.depromeet.whatnow.domains.interaction.service.InteractionDomainService

@UseCase
class InteractionReadUseCase(
val interactionDomainService: InteractionDomainService,
) {

fun findMyInteraction(promiseId: Long): InteractionResponse {
val userId: Long = SecurityUtils.currentUserId
return InteractionResponse(
interactionDomainService.queryAllInteraction(promiseId, userId).map { InteractionDto.from(it) },
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.depromeet.whatnow.api.interaction.usecase

import com.depromeet.whatnow.annotation.UseCase
import com.depromeet.whatnow.config.security.SecurityUtils
import com.depromeet.whatnow.domains.interaction.domain.InteractionType
import com.depromeet.whatnow.domains.interactionhistory.service.InteractionHistoryDomainService

@UseCase
class InteractionSendUseCase(
val interactionHistoryDomainService: InteractionHistoryDomainService,
) {
fun execute(promiseId: Long, interactionType: InteractionType, targetUserId: Long) {
val userId = SecurityUtils.currentUserId
interactionHistoryDomainService.sendInteraction(promiseId, interactionType, userId, targetUserId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.depromeet.whatnow.api.promiseprogress.usecase

import com.depromeet.whatnow.annotation.UseCase
import com.depromeet.whatnow.api.promiseprogress.dto.response.UserProgressResponse
import com.depromeet.whatnow.common.aop.verify.CheckUserParticipation
import com.depromeet.whatnow.domains.progresshistory.adapter.ProgressHistoryAdapter
import com.depromeet.whatnow.domains.promiseuser.adaptor.PromiseUserAdaptor
import com.depromeet.whatnow.domains.user.adapter.UserAdapter
Expand All @@ -12,10 +13,8 @@ class ProgressHistoryReadUseCase(
val promiseUserAdapter: PromiseUserAdaptor,
val userAdapter: UserAdapter,
) {
@CheckUserParticipation
fun execute(promiseId: Long, userId: Long): UserProgressResponse {
// 해당 유저가 해당 약속에 있는지 검증을... 함 해야햐긴함!
// 이부분은 나중에 깔쌈하게 누가 aop로 만들면 참 좋을듯
promiseUserAdapter.findByPromiseIdAndUserId(promiseId, userId)
val history = progressHistoryAdapter.findByPromiseIdAndUserId(promiseId, userId)
return UserProgressResponse(userAdapter.queryUser(userId).toUserInfoVo(), history.currentPromiseProgress, history.prePromiseProgress)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ enum class GlobalErrorCode(val status: Int, val code: String, val reason: String
OTHER_SERVER_INTERNAL_SERVER_ERROR(INTERNAL_SERVER, "OTHER_SERVER_500_1", "다른 서버에서 알 수 없는 서버 오류가 발생했습니다."),

SECURITY_CONTEXT_NOT_FOUND(INTERNAL_SERVER, "GLOBAL_500_2", "security context not found"),

@ExplainError("해당 약속에 유저가 참여하지 않을때 발생하는 오류입니다.")
USER_NOT_PARTICIPATE(BAD_REQUEST, "GLOBAL_400_2", "해당 약속에 유저가 참여하지 않습니다."),

@ExplainError("userId를 Long으로 변환시 발생하는 오류입니다.")
USER_ID_NOT_LONG(INTERNAL_SERVER, "GLOBAL_500_5", "userId를 Long으로 변환시 발생하는 오류입니다."),

@ExplainError("promiseId를 Long으로 변환시 발생하는 오류입니다.")
PROMISE_ID_NOT_LONG(INTERNAL_SERVER, "GLOBAL_500_6", "promiseId를 Long으로 변환시 발생하는 오류입니다."),

@ExplainError("userId가 파라미터에 없을 때 발생하는 오류입니다.")
USER_ID_PARAMETER_NOT_FOUND(BAD_REQUEST, "GLOBAL_400_3", "userId가 파라미터에 없습니다."),

@ExplainError("promiseId가 파라미터에 없을 때 발생하는 오류입니다.")
PROMISE_ID_PARAMETER_NOT_FOUND(BAD_REQUEST, "GLOBAL_400_4", "promiseId가 파라미터에 없습니다."),

;

override val errorReason: ErrorReason
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.depromeet.whatnow.exception.custom

import com.depromeet.whatnow.exception.GlobalErrorCode
import com.depromeet.whatnow.exception.WhatnowCodeException

class NotParticipatedInPromiseException : WhatnowCodeException(
GlobalErrorCode.USER_NOT_PARTICIPATE,
) {
companion object {
val EXCEPTION: WhatnowCodeException = NotParticipatedInPromiseException()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.depromeet.whatnow.exception.custom

import com.depromeet.whatnow.exception.GlobalErrorCode
import com.depromeet.whatnow.exception.WhatnowCodeException

class PromiseIdConversionException : WhatnowCodeException(
GlobalErrorCode.PROMISE_ID_NOT_LONG,
) {
companion object {
val EXCEPTION: WhatnowCodeException = PromiseIdConversionException()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.depromeet.whatnow.exception.custom

import com.depromeet.whatnow.exception.GlobalErrorCode
import com.depromeet.whatnow.exception.WhatnowCodeException

class PromiseIdParameterNotFoundException : WhatnowCodeException(
GlobalErrorCode.PROMISE_ID_PARAMETER_NOT_FOUND,
) {
companion object {
val EXCEPTION: WhatnowCodeException = PromiseIdParameterNotFoundException()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.depromeet.whatnow.exception.custom

import com.depromeet.whatnow.exception.GlobalErrorCode
import com.depromeet.whatnow.exception.WhatnowCodeException

class UserIdConversionException : WhatnowCodeException(
GlobalErrorCode.USER_ID_NOT_LONG,
) {
companion object {
val EXCEPTION: WhatnowCodeException = UserIdConversionException()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.depromeet.whatnow.exception.custom

import com.depromeet.whatnow.exception.GlobalErrorCode
import com.depromeet.whatnow.exception.WhatnowCodeException

class UserIdParameterNotFoundException : WhatnowCodeException(
GlobalErrorCode.USER_ID_PARAMETER_NOT_FOUND,
) {
companion object {
val EXCEPTION: WhatnowCodeException = UserIdParameterNotFoundException()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.depromeet.whatnow.common.aop.verify

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class CheckUserParticipation
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.depromeet.whatnow.common.aop.verify

import com.depromeet.whatnow.domains.promiseuser.adaptor.PromiseUserAdaptor
import com.depromeet.whatnow.domains.promiseuser.exception.PromiseUserNotFoundException
import com.depromeet.whatnow.exception.custom.NotParticipatedInPromiseException
import com.depromeet.whatnow.exception.custom.PromiseIdConversionException
import com.depromeet.whatnow.exception.custom.PromiseIdParameterNotFoundException
import com.depromeet.whatnow.exception.custom.UserIdConversionException
import com.depromeet.whatnow.exception.custom.UserIdParameterNotFoundException
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.reflect.MethodSignature
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression
import org.springframework.stereotype.Component
import java.lang.NumberFormatException

@Aspect
@Component
@ConditionalOnExpression("\${ableCheckUserParticipation:true}")
class CheckUserParticipationAop(
val promiseUserAdaptor: PromiseUserAdaptor,
) {
@Around("@annotation(com.depromeet.whatnow.common.aop.verify.CheckUserParticipation)")
fun verify(joinPoint: ProceedingJoinPoint): Any? {
val signature = joinPoint.signature as MethodSignature
val args = joinPoint.args
val userId = findUserIdArg(signature.parameterNames, args)
val promiseId = findPromiseIdArg(signature.parameterNames, args)

if (userIdInPromise(userId, promiseId)) {
return joinPoint.proceed()
}
throw NotParticipatedInPromiseException.EXCEPTION
}

private fun findUserIdArg(methodParameterNames: Array<String>, args: Array<Any>): Long {
for (i in methodParameterNames.indices) {
if ((methodParameterNames[i] == "userId")) {
val arg = args[i]
if (arg is Long) {
return arg
} else if (arg is String) {
try {
return arg.toLong()
} catch (e: NumberFormatException) {
throw UserIdConversionException.EXCEPTION
}
} else {
UserIdParameterNotFoundException.EXCEPTION
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when 으로 좀더 이쁘게 바꿔볼수 있을것같음!!

}
throw UserIdParameterNotFoundException.EXCEPTION
}

private fun findPromiseIdArg(methodParameterNames: Array<String>, args: Array<Any>): Long {
for (i in methodParameterNames.indices) {
if ((methodParameterNames[i] == "promiseId")) {
val arg = args[i]
if (arg is Long) {
return arg
} else if (arg is String) {
try {
return arg.toLong()
} catch (e: NumberFormatException) {
throw PromiseIdConversionException.EXCEPTION
}
} else {
PromiseIdParameterNotFoundException.EXCEPTION
}
}
}
throw PromiseIdParameterNotFoundException.EXCEPTION
}

private fun userIdInPromise(userId: Long, promiseId: Long): Boolean {
return try {
promiseUserAdaptor.findByPromiseIdAndUserId(promiseId, userId)
true
} catch (e: PromiseUserNotFoundException) {
false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.depromeet.whatnow.domains.interaction.adapter

import com.depromeet.whatnow.annotation.Adapter
import com.depromeet.whatnow.domains.interaction.domain.Interaction
import com.depromeet.whatnow.domains.interaction.domain.InteractionType
import com.depromeet.whatnow.domains.interaction.repository.InteractionRepository

@Adapter
class InteractionAdapter(
val interactionRepository: InteractionRepository,
) {
fun save(interaction: Interaction) {
interactionRepository.save(interaction)
}

fun queryInteraction(promiseId: Long, userId: Long, interactionType: InteractionType): Interaction {
return interactionRepository.findByPromiseIdAndUserIdAndInteractionType(promiseId, userId, interactionType)
}

fun queryAllInteraction(promiseId: Long, userId: Long): List<Interaction> {
return interactionRepository.findAllByPromiseIdAndUserId(promiseId, userId)
}
}
Loading
Loading