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-58] 약속 생성, 삭제, 수정 비즈니스 로직을 작성한다 #72

Merged
merged 16 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from 13 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,5 @@
package com.depromeet.whatnow.api.promise.annotation

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class RequiresMainUser
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.depromeet.whatnow.api.promise.annotation

import com.depromeet.whatnow.config.security.SecurityUtils
import com.depromeet.whatnow.domains.promise.adaptor.PromiseAdaptor
import com.depromeet.whatnow.domains.promise.exception.NotHostException
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.springframework.stereotype.Component

@Aspect
@Component
class RequiresMainUserAspect(
val promiseAdaptor: PromiseAdaptor,
) {
@Around("@annotation(requiresMainUser)")
fun validateMainUserAccess(joinPoint: ProceedingJoinPoint, requiresMainUser: RequiresMainUser): Any? {
val userId = SecurityUtils.currentUserId
val promiseId = joinPoint.args[0] as Long
val promise = promiseAdaptor.queryPromise(promiseId)
if (userId == promise.mainUserId) {
return joinPoint.proceed()
} else {
throw NotHostException()
}
}
Comment on lines +19 to +26
Copy link
Member

Choose a reason for hiding this comment

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

Promise.validMainUser() 이라는 메서드로 만들면 좋겠군요
NotHostException() 도 저희 입셉션 써주시궁

}
Comment on lines +15 to +27
Copy link
Member

Choose a reason for hiding this comment

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

이 안에서
트랜잭션 어노테이션을 활용한 클래스를 만들면
영속성 캐싱의 이점을 노려볼 순 있긴해요~!

근데이건 나중에 고민해 보셔도 좋을 듯

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

AOP 안에서 캐싱하는거 되게 좋은 전략인 것 같아요!
적어뒀다가 반영해보겠습니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.depromeet.whatnow.api.promise.controller

import com.depromeet.whatnow.api.promise.dto.PromiseDto
import com.depromeet.whatnow.api.promise.dto.PromiseFindDto
import com.depromeet.whatnow.api.promise.dto.PromiseRequest
import com.depromeet.whatnow.api.promise.dto.PromiseSplitedByPromiseTypeDto
import com.depromeet.whatnow.api.promise.usecase.PromiseReadUseCase
import com.depromeet.whatnow.api.promise.usecase.PromiseRegisterUseCase
import com.depromeet.whatnow.common.vo.PlaceVo
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.format.annotation.DateTimeFormat
import org.springframework.web.bind.annotation.DeleteMapping
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.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDateTime

@RestController
@RequestMapping("/v1")
@Tag(name = "3. [약속]")
@SecurityRequirement(name = "access-token")
class PromiseController(
val promiseRegisterUseCase: PromiseRegisterUseCase,
val promiseReadUseCase: PromiseReadUseCase,
) {
// 나의 약속 전부 조회
@Operation(summary = "나의 약속 전부 조회", description = "유저의 약속 전부 조회 (단, 예정된 약속과 지난 약속을 구분해서 조회")
@GetMapping("/promises/users")
fun findByPromiseByUser(): List<PromiseSplitedByPromiseTypeDto> {
return promiseReadUseCase.findPromiseByUserIdSeparatedType()
}

@Operation(summary = "월단위 약속 조회", description = "유저의 월간 약속 조회 (단, 예정된 약속과 지난 약속을 구분없이 조회)")
@GetMapping("/promises/users/year-month/{year-month}")
fun findByPromiseByUserAndYearMonth(@PathVariable(value = "year-month") yearMonth: String): List<PromiseFindDto> {
return promiseReadUseCase.findPromiseByUserIdYearMonth(yearMonth)
}

@Operation(summary = "약속(promise) 생성", description = "약속을 생성합니다.")
@PostMapping("/promises")
fun createPromise(@RequestBody promiseRequest: PromiseRequest): PromiseDto {
return promiseRegisterUseCase.createPromise(promiseRequest)
}

// 1. 약속 장소 변경
@Operation(summary = "약속(promise) 장소 수정", description = "약속 장소를 변경합니다.")
@PutMapping("/promises/{promise-id}/location")
fun updatePromiseLocation(@PathVariable(value = "promise-id") promiseId: Long, @RequestBody meetPlace: PlaceVo): PromiseDto {
return promiseRegisterUseCase.updatePromiseMeetPlace(promiseId, meetPlace)
}

@Operation(summary = "약속(promise)시간 수정", description = "약속을 수정합니다. (약속 제목, 약속 장소, 약속 시간)")
@PutMapping("/promises/{promise-id}/end-times/{end-time}")
fun updatePromiseEndTime(
@PathVariable(value = "promise-id") promiseId: Long,
@RequestBody
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
endTime: LocalDateTime,
): PromiseDto {
return promiseRegisterUseCase.updatePromiseEndTime(promiseId, endTime)
}

// 약속 취소
@Operation(summary = "약속 취소", description = "약속을 취소합니다.")
@DeleteMapping("/promises/{promise-id}/user-id/{user-id}")
fun deletePromise(@PathVariable(value = "promise-id") promiseId: Long) {
promiseRegisterUseCase.deletePromise(promiseId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.depromeet.whatnow.api.promise.dto

import com.depromeet.whatnow.common.vo.PlaceVo
import com.depromeet.whatnow.domains.promise.domain.Promise
import java.time.LocalDateTime

data class PromiseDto(
val title: String,
val mainUserId: Long,
val meetPlace: PlaceVo?,
val endTime: LocalDateTime,
) {
companion object {
fun from(p: Promise?): PromiseDto {
// Check if p is null and handle the null case appropriately
if (p == null) {
// Return a default or placeholder value for PromiseDto
return PromiseDto("", 1L, null, LocalDateTime.now())
}

// Process non-null Promise object
return PromiseDto(p.title, p.mainUserId, p.meetPlace, p.endTime)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.depromeet.whatnow.api.promise.dto

import com.depromeet.whatnow.common.vo.UserInfoVo
import com.depromeet.whatnow.domains.promise.domain.Promise
import java.time.LocalDateTime

data class PromiseFindDto(
val title: String,
val address: String,
val endTime: LocalDateTime,
val users: List<UserInfoVo> = listOf(),
) {
companion object {
fun from(promise: Promise, users: List<UserInfoVo>): PromiseFindDto {
val userValues = listOf<UserInfoVo>()
Copy link
Member

Choose a reason for hiding this comment

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

listOf 가 mutable 한가요?

for (user in users) {
userValues.plus(user)
}
Copy link
Member

Choose a reason for hiding this comment

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

listOf가 이뮤터블해서 plus 메소드 내부 동작이 ArrayList를 기존크기 + 1로 새로 할당해서 값을 추가하네용
mutableListOf가 좋아보임니다 👍🏻

Copy link
Member

Choose a reason for hiding this comment

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

image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

plus 연산은 mutableList 도 동일한 원리로 동작합니다 그래서 addAll 로 변환해서 작성했습니다 !

return PromiseFindDto(
promise.title,
promise.meetPlace!!.address,
promise.endTime,
userValues,
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.depromeet.whatnow.api.promise.dto

import com.depromeet.whatnow.common.vo.PlaceVo
import java.time.LocalDateTime

data class PromiseRequest(
val title: String,
val mainUserId: Long,
val meetPlace: PlaceVo,
val endTime: LocalDateTime,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.depromeet.whatnow.api.promise.dto

import com.depromeet.whatnow.common.vo.UserInfoVo
import com.depromeet.whatnow.domains.promise.domain.Promise

data class PromiseSplitedByPromiseTypeDto(
val promiseType: String,
Copy link
Member

Choose a reason for hiding this comment

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

여기서 string 을 내려주지말고
PromiseType 이넘을 그대로 내려줬으면 어땠을 까 하네요
Delete 때문에 그러신거겠죠?

Copy link
Member

Choose a reason for hiding this comment

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

찬진님 저도 이부분 보면서 공감되는데요,
Delete때문에 String으로 넣는다는건 어떤거죠??

val promiseList: List<PromiseFindDto> = listOf(),
) {
fun addPromise(promise: Promise, users: List<UserInfoVo>) {
promiseList.plus(PromiseFindDto.from(promise, users))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.depromeet.whatnow.api.promise.usecase

import com.depromeet.whatnow.annotation.UseCase
import com.depromeet.whatnow.api.promise.dto.PromiseFindDto
import com.depromeet.whatnow.api.promise.dto.PromiseSplitedByPromiseTypeDto
import com.depromeet.whatnow.common.vo.UserInfoVo
import com.depromeet.whatnow.config.security.SecurityUtils
import com.depromeet.whatnow.domains.promise.adaptor.PromiseAdaptor
import com.depromeet.whatnow.domains.promise.domain.Promise
import com.depromeet.whatnow.domains.promise.domain.PromiseType
import com.depromeet.whatnow.domains.promiseuser.adaptor.PromiseUserAdaptor
import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUser
import com.depromeet.whatnow.domains.user.repository.UserRepository
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

@UseCase
class PromiseReadUseCase(
val promiseAdaptor: PromiseAdaptor,
val promiseUserAdaptor: PromiseUserAdaptor,
val userRepository: UserRepository,
) {
/**
* method desc: 유저가 참여한 약속들을 약속 종류(BEFORE, PAST)에 따라 분리해서 조회
* @param userId: 유저 아이디
* @return List<PromiseSplitByPromiseTypeDto>: 약속 종류에 따라 분리된 약속들
*/
fun findPromiseByUserIdSeparatedType(): List<PromiseSplitedByPromiseTypeDto> {
val userId: Long = SecurityUtils.currentUserId

val promiseUsers = promiseUserAdaptor.findByUserId(userId)
val promiseSplitByPromiseTypeDto = mutableListOf<PromiseSplitedByPromiseTypeDto>()

for (promiseUser in promiseUsers) {
val promise = promiseAdaptor.queryPromise(promiseUser.promiseId)
val eachPromiseUsers = promiseUserAdaptor.findByPromiseId(promiseUser.promiseId)
val participant = getParticipantUserInfo(eachPromiseUsers)
val promiseType = if (promise.promiseType == PromiseType.BEFORE) "BEFORE" else "PAST"
Copy link
Member

Choose a reason for hiding this comment

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

Delete 된 상태의 promise도 노출될 가능성이 있지 않을 까요

val promiseFindDto = PromiseFindDto.from(promise, participant)
Copy link
Member

Choose a reason for hiding this comment

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

정적 팩터리메소드
from 이 하나일 때
of 가 여러개일 때 인걸로 알고 있습니다!

val dto = PromiseSplitedByPromiseTypeDto(promiseType, listOf(promiseFindDto))
promiseSplitByPromiseTypeDto += dto
}
return promiseSplitByPromiseTypeDto
}

/**
* method name: findPromiseByUserIdSeparatedWithYearMonth
* description: 유저가 참여한 약속들 중 특정 년월에 해당하는 약속들을 조회한다
*/
fun findPromiseByUserIdYearMonth(yearMonth: String): List<PromiseFindDto> {
val userId: Long = SecurityUtils.currentUserId
return findPromisesByUserId(userId)
.filter { isSameYearMonth(it.endTime, yearMonth) }
.map { promise ->
// 약속에 참여한 유저들
val participants = getParticipantUserInfo(promiseUserAdaptor.findByPromiseId(promise.id!!))
PromiseFindDto(
title = promise.title,
address = promise.meetPlace!!.address,
endTime = promise.endTime,
users = participants,
)
}
}

fun findPromisesByUserId(userId: Long): List<Promise> {
val promiseUsers = promiseUserAdaptor.findByUserId(userId)
return promiseUsers.map { promiseAdaptor.queryPromise(it.promiseId) }
}

private fun getParticipantUserInfo(promiseUsers: List<PromiseUser>): List<UserInfoVo> {
return promiseUsers.mapNotNull { eachUser ->
val user = userRepository.findById(eachUser.userId).orElse(null)
user?.let { UserInfoVo.from(it) }
}
}

private fun isSameYearMonth(dateTime: LocalDateTime, yearMonth: String): Boolean {
val pattern = DateTimeFormatter.ofPattern("yyyy.MM")
val formattedDateTime = dateTime.format(pattern)
return formattedDateTime == yearMonth
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.depromeet.whatnow.api.promise.usecase

import com.depromeet.whatnow.annotation.UseCase
import com.depromeet.whatnow.api.promise.annotation.RequiresMainUser
import com.depromeet.whatnow.api.promise.dto.PromiseDto
import com.depromeet.whatnow.api.promise.dto.PromiseRequest
import com.depromeet.whatnow.common.vo.PlaceVo
import com.depromeet.whatnow.domains.promise.adaptor.PromiseAdaptor
import com.depromeet.whatnow.domains.promise.domain.Promise
import com.depromeet.whatnow.domains.promise.service.PromiseDomainService
import java.time.LocalDateTime
import javax.transaction.Transactional

@UseCase
class PromiseRegisterUseCase(
val promiseAdaptor: PromiseAdaptor,
val promiseDomainService: PromiseDomainService,
) {
@Transactional
fun createPromise(promiseRequest: PromiseRequest): PromiseDto {
val promise = promiseDomainService.save(
Promise(
title = promiseRequest.title,
mainUserId = promiseRequest.mainUserId,
meetPlace = promiseRequest.meetPlace,
endTime = promiseRequest.endTime,
),
)
return PromiseDto.from(promise)
}

@RequiresMainUser
Copy link
Member

Choose a reason for hiding this comment

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

@RequiresMainUser 가 조금 더 보기 편하게 Controller에 붙어있으면 하는데 어떠신가요?!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good

fun updatePromiseMeetPlace(promiseId: Long, meetPlace: PlaceVo): PromiseDto {
val promise = promiseAdaptor.queryPromise(promiseId)
val updatePromise = promiseDomainService.updateMeetPlace(promise, meetPlace)
return PromiseDto.from(updatePromise)
}

@RequiresMainUser
fun updatePromiseEndTime(promiseId: Long, endTime: LocalDateTime): PromiseDto {
val promise = promiseAdaptor.queryPromise(promiseId)
val updatePromise = promiseDomainService.updateEndTime(promise, endTime)
return PromiseDto.from(updatePromise)
}

@RequiresMainUser
fun deletePromise(promiseId: Long) {
val promise = promiseAdaptor.queryPromise(promiseId)
promiseDomainService.deletePromise(promise)
}
}

This file was deleted.

Loading