Skip to content

Commit

Permalink
[Feat/#362] 구독 관리 기능 구현 (#377)
Browse files Browse the repository at this point in the history
* feat: 구독 테이블에 modified_at 칼럼 추가

* feat: 구독 전송 날짜, 시간 수정 쿼리 작성

* feat: 구독 전송 시간 수정 유즈케이스 구현

* feat: 구독 전송 요일 수정 유즈케이스 구현

* feat: 구독 전송 정보 수정 API 구현
  • Loading branch information
belljun3395 authored Sep 6, 2024
1 parent be5d051 commit c4ad770
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,40 @@ class SubscriptionDao(
.from(SUBSCRIPTION)
.where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId))
.and(SUBSCRIPTION.TARGET_WORKBOOK_ID.`in`(query.workbookIds))

fun selectAllActiveSubscriptionWorkbookIds(query: SelectAllActiveSubscriptionWorkbookIdsQuery): List<Long> {
return dslContext.select(SUBSCRIPTION.TARGET_WORKBOOK_ID)
.from(SUBSCRIPTION)
.where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId))
.and(SUBSCRIPTION.DELETED_AT.isNull)
.fetchInto(Long::class.java)
}

data class SelectAllActiveSubscriptionWorkbookIdsQuery(
val memberId: Long,
)

fun bulkUpdateSubscriptionSendTime(command: BulkUpdateSubscriptionSendTimeCommand) {
bulkUpdateSubscriptionSendTimeCommand(command)
.execute()
}

fun bulkUpdateSubscriptionSendTimeCommand(command: BulkUpdateSubscriptionSendTimeCommand) =
dslContext.update(SUBSCRIPTION)
.set(SUBSCRIPTION.SEND_TIME, command.time)
.set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now())
.where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId))
.and(SUBSCRIPTION.TARGET_WORKBOOK_ID.`in`(command.workbookIds))

fun bulkUpdateSubscriptionSendDay(command: BulkUpdateSubscriptionSendDayCommand) {
bulkUpdateSubscriptionSendDayCommand(command)
.execute()
}

fun bulkUpdateSubscriptionSendDayCommand(command: BulkUpdateSubscriptionSendDayCommand) =
dslContext.update(SUBSCRIPTION)
.set(SUBSCRIPTION.SEND_DAY, command.day)
.set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now())
.where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId))
.and(SUBSCRIPTION.TARGET_WORKBOOK_ID.`in`(command.workbookIds))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.few.api.repo.dao.subscription.command

data class BulkUpdateSubscriptionSendDayCommand(
val memberId: Long,
val day: String,
val workbookIds: List<Long>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.few.api.repo.dao.subscription.command

import java.time.LocalTime

data class BulkUpdateSubscriptionSendTimeCommand(
val memberId: Long,
val time: LocalTime,
val workbookIds: List<Long>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.few.api.domain.subscription.usecase

import com.few.api.domain.subscription.usecase.dto.UpdateSubscriptionDayUseCaseIn
import com.few.api.repo.dao.subscription.SubscriptionDao
import com.few.api.repo.dao.subscription.command.BulkUpdateSubscriptionSendDayCommand
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

@Component
class UpdateSubscriptionDayUseCase(
private val subscriptionDao: SubscriptionDao,
) {
@Transactional
fun execute(useCaseIn: UpdateSubscriptionDayUseCaseIn) {
/**
* workbookId기 없으면, memberId로 구독중인 모든 workbookId를 가져와서 해당하는 모든 workbookId의 구독요일을 변경한다.
*/
useCaseIn.workbookId ?: subscriptionDao.selectAllActiveSubscriptionWorkbookIds(
SubscriptionDao.SelectAllActiveSubscriptionWorkbookIdsQuery(useCaseIn.memberId)
).let {
subscriptionDao.bulkUpdateSubscriptionSendDay(
BulkUpdateSubscriptionSendDayCommand(
useCaseIn.memberId,
useCaseIn.dayCode.code,
it
)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.few.api.domain.subscription.usecase

import com.few.api.domain.subscription.usecase.dto.UpdateSubscriptionTimeUseCaseIn
import com.few.api.repo.dao.subscription.SubscriptionDao
import com.few.api.repo.dao.subscription.command.BulkUpdateSubscriptionSendTimeCommand
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

@Component
class UpdateSubscriptionTimeUseCase(
private val subscriptionDao: SubscriptionDao,

) {
@Transactional
fun execute(useCaseIn: UpdateSubscriptionTimeUseCaseIn) {
/**
* workbookId기 없으면, memberId로 구독중인 모든 workbookId를 가져와서 해당하는 모든 workbookId의 구독요일을 변경한다.
*/
useCaseIn.workbookId ?: subscriptionDao.selectAllActiveSubscriptionWorkbookIds(
SubscriptionDao.SelectAllActiveSubscriptionWorkbookIdsQuery(useCaseIn.memberId)
).let {
subscriptionDao.bulkUpdateSubscriptionSendTime(
BulkUpdateSubscriptionSendTimeCommand(
useCaseIn.memberId,
useCaseIn.time,
it
)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.few.api.domain.subscription.usecase.dto

import com.few.api.web.support.DayCode

data class UpdateSubscriptionDayUseCaseIn(
val memberId: Long,
val dayCode: DayCode,
val workbookId: Long?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.few.api.domain.subscription.usecase.dto

import java.time.LocalTime

data class UpdateSubscriptionTimeUseCaseIn(
val memberId: Long,
val time: LocalTime,
val workbookId: Long?,
)
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.few.api.web.controller.subscription

import com.few.api.domain.subscription.usecase.BrowseSubscribeWorkbooksUseCase
import com.few.api.domain.subscription.usecase.*
import com.few.api.web.controller.subscription.request.UnsubscribeWorkbookRequest
import com.few.api.web.support.ApiResponse
import com.few.api.web.support.ApiResponseGenerator
import com.few.api.domain.subscription.usecase.SubscribeWorkbookUseCase
import com.few.api.domain.subscription.usecase.UnsubscribeAllUseCase
import com.few.api.domain.subscription.usecase.UnsubscribeWorkbookUseCase
import com.few.api.domain.subscription.usecase.dto.*
import com.few.api.security.authentication.token.TokenUserDetails
import com.few.api.web.controller.subscription.request.UnsubscribeAllRequest
import com.few.api.web.controller.subscription.request.UpdateSubscriptionDayRequest
import com.few.api.web.controller.subscription.request.UpdateSubscriptionTimeRequest
import com.few.api.web.controller.subscription.response.*
import com.few.api.web.support.DayCode
import com.few.api.web.support.ViewCategory
import jakarta.validation.Valid
import jakarta.validation.constraints.Min
Expand All @@ -29,6 +29,8 @@ class SubscriptionController(
private val unsubscribeWorkbookUseCase: UnsubscribeWorkbookUseCase,
private val unsubscribeAllUseCase: UnsubscribeAllUseCase,
private val browseSubscribeWorkbooksUseCase: BrowseSubscribeWorkbooksUseCase,
private val updateSubscriptionDayUseCase: UpdateSubscriptionDayUseCase,
private val updateSubscriptionTimeUseCase: UpdateSubscriptionTimeUseCase,
) {

@GetMapping("/subscriptions/workbooks")
Expand Down Expand Up @@ -130,4 +132,42 @@ class SubscriptionController(

return ApiResponseGenerator.success(HttpStatus.OK)
}

@PatchMapping("/subscriptions/time")
fun updateSubscriptionTime(
@AuthenticationPrincipal userDetails: TokenUserDetails,
@Valid @RequestBody
body: UpdateSubscriptionTimeRequest,
): ApiResponse<ApiResponse.Success> {
UpdateSubscriptionTimeUseCaseIn(
memberId = userDetails.username.toLong(),
time = body.time,
workbookId = body.workbookId
).let {
updateSubscriptionTimeUseCase.execute(it)
}
return ApiResponseGenerator.success(HttpStatus.OK)
}

@PatchMapping("/subscriptions/day")
fun updateSubscriptionDay(
@AuthenticationPrincipal userDetails: TokenUserDetails,
@Valid @RequestBody
body: UpdateSubscriptionDayRequest,
): ApiResponse<ApiResponse.Success> {
val dayCode = DayCode.fromCode(body.dayCode)
dayCode.also {
if (!(it == (DayCode.MON_TUE_WED_THU_FRI_SAT_SUN) || it == (DayCode.MON_TUE_WED_THU_FRI))) {
throw IllegalArgumentException("Invalid day code")
}
}
UpdateSubscriptionDayUseCaseIn(
memberId = userDetails.username.toLong(),
dayCode = dayCode,
workbookId = body.workbookId
).let {
updateSubscriptionDayUseCase.execute(it)
}
return ApiResponseGenerator.success(HttpStatus.OK)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.few.api.web.controller.subscription.request

data class UpdateSubscriptionDayRequest(
val dayCode: String,
val workbookId: Long?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.few.api.web.controller.subscription.request

import java.time.LocalTime

data class UpdateSubscriptionTimeRequest(
val time: LocalTime,
val workbookId: Long?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import com.few.api.web.controller.description.Description
import com.few.api.web.controller.subscription.request.UnsubscribeWorkbookRequest
import com.few.api.domain.subscription.usecase.dto.*
import com.few.api.web.controller.helper.*
import com.few.api.web.controller.subscription.request.UpdateSubscriptionDayRequest
import com.few.api.web.controller.subscription.request.UpdateSubscriptionTimeRequest
import com.few.api.web.support.DayCode
import com.few.api.web.support.ViewCategory
import com.few.api.web.support.WorkBookStatus
import org.junit.jupiter.api.DisplayName
Expand All @@ -22,6 +25,7 @@ import org.springframework.restdocs.payload.PayloadDocumentation
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import org.springframework.web.util.UriComponentsBuilder
import java.time.LocalTime

class SubscriptionControllerTest : ControllerTestSpec() {

Expand Down Expand Up @@ -407,5 +411,113 @@ class SubscriptionControllerTest : ControllerTestSpec() {
)
)
)

@Test
@DisplayName("[PATCH] /api/v1/subscriptions/time")
@WithUserDetails(userDetailsServiceBeanName = "testTokenUserDetailsService")
fun updateSubscriptionTime() {
// given
val api = "UpdateSubscriptionTime"
val token = "thisisaccesstoken"
val uri = UriComponentsBuilder.newInstance()
.path("$BASE_URL/subscriptions/time")
.build()
.toUriString()

val time = LocalTime.of(8, 0)
val workbookId = 1L
val body = objectMapper.writeValueAsString(
UpdateSubscriptionTimeRequest(
time = time,
workbookId = workbookId
)
)

// when
mockMvc.perform(
patch(uri)
.header("Authorization", "Bearer $token")
.content(body)
.contentType(MediaType.APPLICATION_JSON)
).andExpect(MockMvcResultMatchers.status().is2xxSuccessful)
.andDo(
document(
api.toIdentifier(),
ResourceDocumentation.resource(
ResourceSnippetParameters.builder()
.description("구독 시간을 변경합니다.")
.summary(api.toIdentifier())
.privateResource(false)
.deprecated(false)
.tag(TAG)
.requestSchema(Schema.schema(api.toRequestSchema()))
.requestHeaders(
ResourceDocumentation.headerWithName("Authorization")
.defaultValue("{{accessToken}}")
.description("Bearer 어세스 토큰")
)
.responseSchema(Schema.schema(api.toResponseSchema()))
.responseFields(
*Description.describe()
)
.build()
)
)
)
}

@Test
@DisplayName("[PATCH] /api/v1/subscriptions/day")
@WithUserDetails(userDetailsServiceBeanName = "testTokenUserDetailsService")
fun updateSubscriptionDay() {
// given
val api = "UpdateSubscriptionDay"
val token = "thisisaccesstoken"
val uri = UriComponentsBuilder.newInstance()
.path("$BASE_URL/subscriptions/day")
.build()
.toUriString()

val dateTimeCode = DayCode.MON_TUE_WED_THU_FRI_SAT_SUN
val workbookId = 1L
val body = objectMapper.writeValueAsString(
UpdateSubscriptionDayRequest(
workbookId = workbookId,
dayCode = dateTimeCode.code
)
)

// when
mockMvc.perform(
patch(uri)
.header("Authorization", "Bearer $token")
.content(body)
.contentType(MediaType.APPLICATION_JSON)
).andExpect(MockMvcResultMatchers.status().is2xxSuccessful)
.andDo(
document(
api.toIdentifier(),
ResourceDocumentation.resource(
ResourceSnippetParameters.builder()
.description("구독 요일을 변경합니다.")
.summary(api.toIdentifier())
.privateResource(false)
.deprecated(false)
.tag(TAG)
.requestSchema(Schema.schema(api.toRequestSchema()))
.requestHeaders(
ResourceDocumentation.headerWithName("Authorization")
.defaultValue("{{accessToken}}")
.description("Bearer 어세스 토큰")
)
.responseSchema(Schema.schema(api.toResponseSchema()))
.responseFields(
*Description.describe()
)
.build()
)
)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- 구독 정보 수정 시간을 추가합니다.
ALTER TABLE SUBSCRIPTION ADD COLUMN modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;

0 comments on commit c4ad770

Please sign in to comment.