From 336d3c94340bf748e906d448f18d4d6197d2c395 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 21 Apr 2025 14:22:10 +0900 Subject: [PATCH 01/25] =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=ED=96=89=EB=8F=99?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0,=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20-?= =?UTF-8?q?=20Entity=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/content/AudioContent.kt | 1 + .../vividnext/sodalive/content/order/Order.kt | 1 + .../vividnext/sodalive/point/MemberPoint.kt | 17 +++++++++++++++++ .../vividnext/sodalive/point/PointGrantLog.kt | 16 ++++++++++++++++ .../sodalive/point/PointRewardPolicy.kt | 19 +++++++++++++++++++ .../co/vividnext/sodalive/point/UsePoint.kt | 10 ++++++++++ .../sodalive/useraction/ActionType.kt | 5 +++++ .../sodalive/useraction/UserActionLog.kt | 13 +++++++++++++ 8 files changed, 82 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/MemberPoint.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLog.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/UsePoint.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/useraction/ActionType.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContent.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContent.kt index 00d3eb3..f65ece2 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContent.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContent.kt @@ -42,6 +42,7 @@ data class AudioContent( val isGeneratePreview: Boolean = true, var isOnlyRental: Boolean = false, var isAdult: Boolean = false, + var isPointAvailable: Boolean = false, var isCommentAvailable: Boolean = true, var isFullDetailVisible: Boolean = true ) : BaseEntity() { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/Order.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/Order.kt index eb59754..391908b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/Order.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/Order.kt @@ -25,6 +25,7 @@ data class Order( var isActive: Boolean = true ) : BaseEntity() { var can: Int = 0 + var point: Int = 0 val startDate: LocalDateTime = LocalDateTime.now() var endDate: LocalDateTime? = null diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/MemberPoint.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/MemberPoint.kt new file mode 100644 index 0000000..912f45c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/MemberPoint.kt @@ -0,0 +1,17 @@ +package kr.co.vividnext.sodalive.point + +import kr.co.vividnext.sodalive.common.BaseEntity +import kr.co.vividnext.sodalive.useraction.ActionType +import java.time.LocalDateTime +import javax.persistence.Entity +import javax.persistence.EnumType +import javax.persistence.Enumerated + +@Entity +data class MemberPoint( + val memberId: Long, + var point: Int, + @Enumerated(EnumType.STRING) + val actionType: ActionType, + val expiresAt: LocalDateTime +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLog.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLog.kt new file mode 100644 index 0000000..780eccc --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLog.kt @@ -0,0 +1,16 @@ +package kr.co.vividnext.sodalive.point + +import kr.co.vividnext.sodalive.common.BaseEntity +import kr.co.vividnext.sodalive.useraction.ActionType +import javax.persistence.Entity +import javax.persistence.EnumType +import javax.persistence.Enumerated + +@Entity +data class PointGrantLog( + val memberId: Long, + val point: Int, + @Enumerated(EnumType.STRING) + val actionType: ActionType, + val policyId: Long? +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt new file mode 100644 index 0000000..070af83 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt @@ -0,0 +1,19 @@ +package kr.co.vividnext.sodalive.point + +import kr.co.vividnext.sodalive.common.BaseEntity +import kr.co.vividnext.sodalive.useraction.ActionType +import java.time.LocalDateTime +import javax.persistence.Entity +import javax.persistence.EnumType +import javax.persistence.Enumerated + +@Entity +data class PointRewardPolicy( + @Enumerated(EnumType.STRING) + val actionType: ActionType, + val threshold: Int, + val pointAmount: Int, + var startDate: LocalDateTime, + var endDate: LocalDateTime? = null, + var isActive: Boolean = true +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePoint.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePoint.kt new file mode 100644 index 0000000..afbeb7e --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePoint.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.point + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Entity + +@Entity +data class UsePoint( + val memberId: Long, + val amount: Int +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/ActionType.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/ActionType.kt new file mode 100644 index 0000000..5b093f3 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/ActionType.kt @@ -0,0 +1,5 @@ +package kr.co.vividnext.sodalive.useraction + +enum class ActionType(val displayName: String) { + SIGN_UP("회원가입") +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt new file mode 100644 index 0000000..fc92561 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt @@ -0,0 +1,13 @@ +package kr.co.vividnext.sodalive.useraction + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Entity +import javax.persistence.EnumType +import javax.persistence.Enumerated + +@Entity +data class UserActionLog( + val memberId: Long, + @Enumerated(EnumType.STRING) + val actionType: ActionType +) : BaseEntity() From 1cb2ee77b50e563682e83a3e7171a7ae6fe9ba0d Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 21 Apr 2025 14:35:05 +0900 Subject: [PATCH 02/25] =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A7=80?= =?UTF-8?q?=EA=B8=89=20=EC=A0=95=EC=B1=85=20-=20Title=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt index 070af83..a898e1c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt @@ -9,6 +9,7 @@ import javax.persistence.Enumerated @Entity data class PointRewardPolicy( + var title: String, @Enumerated(EnumType.STRING) val actionType: ActionType, val threshold: Int, From d94418067fbf37cc615c709264475be2a9f32aaa Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 21 Apr 2025 19:08:31 +0900 Subject: [PATCH 03/25] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EC=A7=80=EA=B8=89=20=EC=A0=95=EC=B1=85=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8,=20=EC=83=9D=EC=84=B1,=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../point/CreatePointRewardPolicyRequest.kt | 37 +++++++++++++ .../point/GetPointRewardPolicyResponse.kt | 15 +++++ .../point/ModifyPointRewardPolicyRequest.kt | 8 +++ .../admin/point/PointPolicyController.kt | 36 ++++++++++++ .../admin/point/PointPolicyRepository.kt | 55 +++++++++++++++++++ .../admin/point/PointPolicyService.kt | 49 +++++++++++++++++ 6 files changed, 200 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/point/ModifyPointRewardPolicyRequest.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt new file mode 100644 index 0000000..a466a32 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt @@ -0,0 +1,37 @@ +package kr.co.vividnext.sodalive.admin.point + +import kr.co.vividnext.sodalive.point.PointRewardPolicy +import kr.co.vividnext.sodalive.useraction.ActionType +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter + +data class CreatePointRewardPolicyRequest( + val title: String, + val actionType: ActionType, + val threshold: Int, + val pointAmount: Int, + val startDate: String, + val endDate: String, + val isActive: Boolean +) { + fun toEntity(): PointRewardPolicy { + val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + + return PointRewardPolicy( + title = title, + actionType = actionType, + threshold = threshold, + pointAmount = pointAmount, + startDate = LocalDateTime.parse(startDate, dateTimeFormatter) + .atZone(ZoneId.of("Asia/Seoul")) + .withZoneSameInstant(ZoneId.of("UTC")) + .toLocalDateTime(), + endDate = LocalDateTime.parse(endDate, dateTimeFormatter).withSecond(59) + .atZone(ZoneId.of("Asia/Seoul")) + .withZoneSameInstant(ZoneId.of("UTC")) + .toLocalDateTime(), + isActive = isActive + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyResponse.kt new file mode 100644 index 0000000..4e8203d --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyResponse.kt @@ -0,0 +1,15 @@ +package kr.co.vividnext.sodalive.admin.point + +import com.querydsl.core.annotations.QueryProjection +import kr.co.vividnext.sodalive.useraction.ActionType + +data class GetPointRewardPolicyResponse @QueryProjection constructor( + val id: Long, + val title: String, + val actionType: ActionType, + val threshold: Int, + val pointAmount: Int, + val startDate: String, + val endDate: String, + val isActive: Boolean +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/ModifyPointRewardPolicyRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/ModifyPointRewardPolicyRequest.kt new file mode 100644 index 0000000..3a4bc11 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/ModifyPointRewardPolicyRequest.kt @@ -0,0 +1,8 @@ +package kr.co.vividnext.sodalive.admin.point + +data class ModifyPointRewardPolicyRequest( + val title: String?, + val startDate: String?, + val endDate: String?, + val isActive: Boolean? +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyController.kt new file mode 100644 index 0000000..12d4dc9 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyController.kt @@ -0,0 +1,36 @@ +package kr.co.vividnext.sodalive.admin.point + +import kr.co.vividnext.sodalive.common.ApiResponse +import org.springframework.data.domain.Pageable +import org.springframework.security.access.prepost.PreAuthorize +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 + +@RestController +@RequestMapping("/admin/point-policies") +@PreAuthorize("hasRole('ADMIN')") +class PointPolicyController(private val service: PointPolicyService) { + @GetMapping + fun getAll(pageable: Pageable) = ApiResponse.ok( + service.getAll( + offset = pageable.offset, + limit = pageable.pageSize.toLong() + ) + ) + + @PostMapping + fun create( + @RequestBody request: CreatePointRewardPolicyRequest + ) = ApiResponse.ok(service.create(request)) + + @PutMapping("/{id}") + fun update( + @PathVariable id: Long, + @RequestBody request: ModifyPointRewardPolicyRequest + ) = ApiResponse.ok(service.update(id, request)) +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt new file mode 100644 index 0000000..3c644d5 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt @@ -0,0 +1,55 @@ +package kr.co.vividnext.sodalive.admin.point + +import com.querydsl.core.types.dsl.DateTimePath +import com.querydsl.core.types.dsl.Expressions +import com.querydsl.core.types.dsl.StringTemplate +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.point.PointRewardPolicy +import kr.co.vividnext.sodalive.point.QPointRewardPolicy.pointRewardPolicy +import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDateTime + +interface PointPolicyRepository : JpaRepository, PointPolicyQueryRepository + +interface PointPolicyQueryRepository { + fun getAll(offset: Long, limit: Long): List +} + +class PointPolicyQueryRepositoryImpl( + private val queryFactory: JPAQueryFactory +) : PointPolicyQueryRepository { + override fun getAll(offset: Long, limit: Long): List { + return queryFactory + .select( + QGetPointRewardPolicyResponse( + pointRewardPolicy.id, + pointRewardPolicy.title, + pointRewardPolicy.actionType, + pointRewardPolicy.threshold, + pointRewardPolicy.pointAmount, + getFormattedDate(pointRewardPolicy.startDate), + getFormattedDate(pointRewardPolicy.endDate), + pointRewardPolicy.isActive + ) + ) + .from(pointRewardPolicy) + .orderBy(pointRewardPolicy.isActive.desc(), pointRewardPolicy.startDate.desc()) + .offset(offset) + .limit(limit) + .fetch() + } + + private fun getFormattedDate(dateTimePath: DateTimePath): StringTemplate { + return Expressions.stringTemplate( + "DATE_FORMAT({0}, {1})", + Expressions.dateTimeTemplate( + LocalDateTime::class.java, + "CONVERT_TZ({0},{1},{2})", + dateTimePath, + "UTC", + "Asia/Seoul" + ), + "%Y-%m-%d %H:%i" + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt new file mode 100644 index 0000000..d5b8c54 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt @@ -0,0 +1,49 @@ +package kr.co.vividnext.sodalive.admin.point + +import kr.co.vividnext.sodalive.common.SodaException +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter + +@Service +class PointPolicyService(private val repository: PointPolicyRepository) { + fun getAll(offset: Long, limit: Long): List { + return repository.getAll(offset, limit) + } + + fun create(request: CreatePointRewardPolicyRequest) { + val pointPolicy = request.toEntity() + repository.save(pointPolicy) + } + + fun update(id: Long, request: ModifyPointRewardPolicyRequest) { + val pointPolicy = repository.findByIdOrNull(id) + ?: throw SodaException("잘못된 접근입니다.") + + val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + + if (request.title != null) { + pointPolicy.title = request.title + } + + if (request.startDate != null) { + pointPolicy.startDate = LocalDateTime.parse(request.startDate, dateTimeFormatter) + .atZone(ZoneId.of("Asia/Seoul")) + .withZoneSameInstant(ZoneId.of("UTC")) + .toLocalDateTime() + } + + if (request.endDate != null) { + pointPolicy.endDate = LocalDateTime.parse(request.endDate, dateTimeFormatter).withSecond(59) + .atZone(ZoneId.of("Asia/Seoul")) + .withZoneSameInstant(ZoneId.of("UTC")) + .toLocalDateTime() + } + + if (request.isActive != null) { + pointPolicy.isActive = request.isActive + } + } +} From e2c70de2e081b6804724c0c4860ab205853c60e4 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 21 Apr 2025 22:03:58 +0900 Subject: [PATCH 04/25] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20=ED=96=89?= =?UTF-8?q?=EB=8F=99=20=EA=B8=B0=EB=A1=9D=20=EB=B0=8F=20=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=A7=80=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20+=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/member/MemberController.kt | 10 ++- .../sodalive/point/MemberPointRepository.kt | 5 ++ .../sodalive/point/PointGrantLogRepository.kt | 27 ++++++++ .../point/PointRewardPolicyRepository.kt | 34 ++++++++++ .../useraction/UserActionLogRepository.kt | 39 ++++++++++++ .../sodalive/useraction/UserActionService.kt | 62 +++++++++++++++++++ 6 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/MemberPointRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLogRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt index 0c55544..e26be41 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt @@ -13,6 +13,8 @@ import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingReq import kr.co.vividnext.sodalive.member.signUp.SignUpRequestV2 import kr.co.vividnext.sodalive.member.social.google.GoogleAuthService import kr.co.vividnext.sodalive.member.social.kakao.KakaoAuthService +import kr.co.vividnext.sodalive.useraction.ActionType +import kr.co.vividnext.sodalive.useraction.UserActionService import org.springframework.data.domain.Pageable import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.userdetails.User @@ -34,7 +36,8 @@ class MemberController( private val service: MemberService, private val kakaoAuthService: KakaoAuthService, private val googleAuthService: GoogleAuthService, - private val trackingService: AdTrackingService + private val trackingService: AdTrackingService, + private val userActionService: UserActionService ) { @GetMapping("/check/email") fun checkEmail(@RequestParam email: String) = service.duplicateCheckEmail(email) @@ -60,6 +63,11 @@ class MemberController( ) } + userActionService.recordAction( + memberId = response.memberId, + actionType = ActionType.SIGN_UP + ) + return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/MemberPointRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/MemberPointRepository.kt new file mode 100644 index 0000000..1fec31b --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/MemberPointRepository.kt @@ -0,0 +1,5 @@ +package kr.co.vividnext.sodalive.point + +import org.springframework.data.jpa.repository.JpaRepository + +interface MemberPointRepository : JpaRepository diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt new file mode 100644 index 0000000..361a873 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt @@ -0,0 +1,27 @@ +package kr.co.vividnext.sodalive.point + +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.point.QPointGrantLog.pointGrantLog +import org.springframework.data.jpa.repository.JpaRepository + +interface PointGrantLogRepository : JpaRepository, PointGrantLogQueryRepository + +interface PointGrantLogQueryRepository { + fun existsByMemberIdAndPolicyId(memberId: Long, policyId: Long): Boolean +} + +class PointGrantLogQueryRepositoryImpl( + private val queryFactory: JPAQueryFactory +) : PointGrantLogQueryRepository { + override fun existsByMemberIdAndPolicyId(memberId: Long, policyId: Long): Boolean { + return queryFactory + .select(pointGrantLog.id) + .from(pointGrantLog) + .where( + pointGrantLog.memberId.eq(memberId), + pointGrantLog.policyId.eq(policyId) + ) + .fetch() + .isNotEmpty() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt new file mode 100644 index 0000000..9587517 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt @@ -0,0 +1,34 @@ +package kr.co.vividnext.sodalive.point + +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.point.QPointRewardPolicy.pointRewardPolicy +import kr.co.vividnext.sodalive.useraction.ActionType +import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDateTime + +interface PointRewardPolicyRepository : JpaRepository, PointRewardPolicyQueryRepository + +interface PointRewardPolicyQueryRepository { + fun findByActionTypeAndIsActiveTrue(actionType: ActionType, nowDateTime: LocalDateTime): PointRewardPolicy? +} + +class PointRewardPolicyQueryRepositoryImpl( + private val queryFactory: JPAQueryFactory +) : PointRewardPolicyQueryRepository { + override fun findByActionTypeAndIsActiveTrue( + actionType: ActionType, + nowDateTime: LocalDateTime + ): PointRewardPolicy? { + return queryFactory + .selectFrom(pointRewardPolicy) + .where( + pointRewardPolicy.isActive, + pointRewardPolicy.actionType.eq(actionType), + pointRewardPolicy.startDate.loe(nowDateTime), + pointRewardPolicy.endDate.goe(nowDateTime) + .or(pointRewardPolicy.endDate.isNull) + ) + .orderBy(pointRewardPolicy.endDate.asc()) + .fetchFirst() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLogRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLogRepository.kt new file mode 100644 index 0000000..0a0f5ec --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLogRepository.kt @@ -0,0 +1,39 @@ +package kr.co.vividnext.sodalive.useraction + +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.useraction.QUserActionLog.userActionLog +import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDateTime + +interface UserActionLogRepository : JpaRepository, UserActionLogQueryRepository + +interface UserActionLogQueryRepository { + fun countByMemberIdAndActionTypeAndCreatedAtBetween( + memberId: Long, + actionType: ActionType, + startDate: LocalDateTime, + endDate: LocalDateTime + ): Int +} + +class UserActionLogQueryRepositoryImpl( + private val queryFactory: JPAQueryFactory +) : UserActionLogQueryRepository { + override fun countByMemberIdAndActionTypeAndCreatedAtBetween( + memberId: Long, + actionType: ActionType, + startDate: LocalDateTime, + endDate: LocalDateTime + ): Int { + return queryFactory + .select(userActionLog.id) + .from(userActionLog) + .where( + userActionLog.memberId.eq(memberId) + .and(userActionLog.actionType.eq(actionType)) + .and(userActionLog.createdAt.between(startDate, endDate)) + ) + .fetch() + .size + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt new file mode 100644 index 0000000..1128897 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -0,0 +1,62 @@ +package kr.co.vividnext.sodalive.useraction + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kr.co.vividnext.sodalive.point.MemberPoint +import kr.co.vividnext.sodalive.point.MemberPointRepository +import kr.co.vividnext.sodalive.point.PointGrantLog +import kr.co.vividnext.sodalive.point.PointGrantLogRepository +import kr.co.vividnext.sodalive.point.PointRewardPolicyRepository +import org.springframework.stereotype.Service +import java.time.LocalDateTime + +@Service +class UserActionService( + private val repository: UserActionLogRepository, + private val policyRepository: PointRewardPolicyRepository, + private val grantLogRepository: PointGrantLogRepository, + private val memberPointRepository: MemberPointRepository +) { + + private val coroutineScope = CoroutineScope(Dispatchers.IO) + + fun recordAction(memberId: Long, actionType: ActionType) { + coroutineScope.launch { + val now = LocalDateTime.now() + repository.save(UserActionLog(memberId, actionType)) + + val policy = policyRepository.findByActionTypeAndIsActiveTrue(actionType, now) + if (policy != null) { + val actionCount = repository.countByMemberIdAndActionTypeAndCreatedAtBetween( + memberId = memberId, + actionType = actionType, + startDate = policy.startDate, + endDate = policy.endDate ?: now + ) + if (actionCount < policy.threshold) return@launch + + val alreadyGranted = grantLogRepository.existsByMemberIdAndPolicyId(memberId, policy.id!!) + if (alreadyGranted) return@launch + + memberPointRepository.save( + MemberPoint( + memberId = memberId, + point = policy.pointAmount, + actionType = actionType, + expiresAt = now.plusDays(3) + ) + ) + + grantLogRepository.save( + PointGrantLog( + memberId = memberId, + point = policy.pointAmount, + actionType = actionType, + policyId = policy.id!! + ) + ) + } + } + } +} From 51dae0f02c689ebc11d3b0cbcdda00c85a03a5b7 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Apr 2025 15:39:45 +0900 Subject: [PATCH 05/25] =?UTF-8?q?feat:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(=EB=A7=8C=EB=A3=8C=EC=9D=BC=20=EC=88=9C=20+=2010=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EB=8B=A8=EC=9C=84=20=EC=B0=A8=EA=B0=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/content/order/OrderService.kt | 11 ++++- .../sodalive/point/MemberPointRepository.kt | 26 ++++++++++- .../sodalive/point/PointUsageService.kt | 43 +++++++++++++++++++ .../sodalive/point/UsePointRepository.kt | 5 +++ 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/PointUsageService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt index 630b22a..81e460f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt @@ -10,6 +10,7 @@ import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository import kr.co.vividnext.sodalive.content.like.AudioContentLikeRepository import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.point.PointUsageService import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -19,6 +20,7 @@ import java.time.LocalDateTime @Transactional(readOnly = true) class OrderService( private val repository: OrderRepository, + private val pointUsageService: PointUsageService, private val canPaymentService: CanPaymentService, private val audioContentRepository: AudioContentRepository, private val audioContentCommentQueryRepository: AudioContentCommentRepository, @@ -41,9 +43,16 @@ class OrderService( orderContent(orderType, content, member) } + val usedPoint = if (order.type == OrderType.RENTAL) { + pointUsageService.usePoint(member.id!!, order.can) + } else { + 0 + } + order.point = usedPoint + canPaymentService.spendCan( memberId = member.id!!, - needCan = order.can, + needCan = order.can - (usedPoint / 10), canUsage = CanUsage.ORDER_CONTENT, order = order, container = container diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/MemberPointRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/MemberPointRepository.kt index 1fec31b..d8aec5d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/MemberPointRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/MemberPointRepository.kt @@ -1,5 +1,29 @@ package kr.co.vividnext.sodalive.point +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.point.QMemberPoint.memberPoint import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDateTime -interface MemberPointRepository : JpaRepository +interface MemberPointRepository : JpaRepository, MemberPointQueryRepository + +interface MemberPointQueryRepository { + fun findByMemberIdAndExpiresAtAfterOrderByExpiresAtAsc(memberId: Long, expiresAt: LocalDateTime): List +} + +class MemberPointQueryRepositoryImpl( + private val queryFactory: JPAQueryFactory +) : MemberPointQueryRepository { + override fun findByMemberIdAndExpiresAtAfterOrderByExpiresAtAsc( + memberId: Long, + expiresAt: LocalDateTime + ): List { + return queryFactory + .selectFrom(memberPoint) + .where( + memberPoint.memberId.eq(memberId), + memberPoint.expiresAt.goe(expiresAt) + ) + .fetch() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointUsageService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointUsageService.kt new file mode 100644 index 0000000..ed02ca1 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointUsageService.kt @@ -0,0 +1,43 @@ +package kr.co.vividnext.sodalive.point + +import org.springframework.stereotype.Service +import java.time.LocalDateTime + +@Service +class PointUsageService( + private val memberPointRepository: MemberPointRepository, + private val usePointRepository: UsePointRepository +) { + fun usePoint(memberId: Long, contentPrice: Int): Int { + val now = LocalDateTime.now() + val maxUsablePoint = contentPrice * 10 + + val points = memberPointRepository.findByMemberIdAndExpiresAtAfterOrderByExpiresAtAsc( + memberId = memberId, + expiresAt = now + ) + + val totalAvailable = points.sumOf { it.point } + val usablePoint = minOf(totalAvailable, maxUsablePoint).floorToNearest10() + + var remaining = usablePoint + var used = 0 + + for (p in points) { + if (remaining <= 0) break + val usable = minOf(p.point, remaining) + p.point -= usable + remaining -= usable + used += usable + } + + if (used > 0) { + memberPointRepository.saveAll(points) + usePointRepository.save(UsePoint(memberId = memberId, amount = used)) + } + + return used + } + + private fun Int.floorToNearest10(): Int = (this / 10) * 10 +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt new file mode 100644 index 0000000..49a5fcc --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt @@ -0,0 +1,5 @@ +package kr.co.vividnext.sodalive.point + +import org.springframework.data.jpa.repository.JpaRepository + +interface UsePointRepository : JpaRepository From 971683a81edeffae34a4ff1c44b6c739adee002d Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Apr 2025 17:35:47 +0900 Subject: [PATCH 06/25] =?UTF-8?q?feat:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=A7=80=EA=B8=89=20=EC=8B=9C=20FCM=20data-only=20=ED=91=B8?= =?UTF-8?q?=EC=8B=9C=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A0=84=EC=86=A1=20?= =?UTF-8?q?=EB=B0=8F=20=EC=8B=A4=ED=8C=A8=20=EC=8B=9C=20=EC=9E=AC=EC=8B=9C?= =?UTF-8?q?=EB=8F=84=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../co/vividnext/sodalive/fcm/FcmService.kt | 53 ++++++++++++++++++- .../sodalive/member/MemberController.kt | 19 +++++-- .../sodalive/member/MemberService.kt | 17 +++++- .../sodalive/member/login/LoginRequest.kt | 6 ++- .../sodalive/member/signUp/SignUpRequest.kt | 1 + .../member/social/google/GoogleAuthService.kt | 9 +++- .../member/social/kakao/KakaoAuthService.kt | 9 +++- .../sodalive/useraction/UserActionService.kt | 11 +++- 8 files changed, 111 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmService.kt index 3b0a9ab..1f96de1 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmService.kt @@ -4,6 +4,9 @@ import com.google.firebase.messaging.AndroidConfig import com.google.firebase.messaging.ApnsConfig import com.google.firebase.messaging.Aps import com.google.firebase.messaging.FirebaseMessaging +import com.google.firebase.messaging.FirebaseMessagingException +import com.google.firebase.messaging.Message +import com.google.firebase.messaging.MessagingErrorCode import com.google.firebase.messaging.MulticastMessage import com.google.firebase.messaging.Notification import org.slf4j.LoggerFactory @@ -82,8 +85,54 @@ class FcmService { } val response = FirebaseMessaging.getInstance().sendEachForMulticast(multicastMessage.build()) - logger.info("보내기 성공: ${response.successCount}") - logger.info("보내기 실패: ${response.failureCount}") + logger.info("[FCM] ✅ 성공: ${response.successCount}") + logger.info("[FCM] ❌ 실패: ${response.failureCount}") + } + } + + fun sendPointGranted(token: String, point: Int) { + val data = mapOf( + "type" to "POINT_GRANTED", + "point" to point.toString(), + "message" to "${point}포인트가 지급되었습니다!" + ) + + var attempts = 0 + val maxAttempts = 3 + + while (attempts < maxAttempts) { + try { + val message = Message.builder() + .setToken(token) + .putAllData(data) + .build() + + val response = FirebaseMessaging.getInstance().send(message) + logger.info("[FCM] ✅ 성공 (attempt ${attempts + 1}): messageId=$response") + return // 성공 시 즉시 종료 + } catch (e: FirebaseMessagingException) { + attempts++ + + // "registration-token-not-registered" 예외 코드 확인 + if (e.messagingErrorCode == MessagingErrorCode.UNREGISTERED) { + logger.error("[FCM] ❌ 실패: 토큰이 등록되지 않음 (등록 해제됨) → 재시도 안함") + return + } + + logger.error("[FCM] ❌ 실패 (attempt $attempts): ${e.errorCode} - ${e.message}") + + if (attempts >= maxAttempts) { + logger.error("[FCM] ❌ 최종 실패: 전송 불가") + } + } catch (e: Exception) { + // Firebase 이외의 예외도 잡기 + attempts++ + logger.error("[FCM] ❌ 실패 (attempt $attempts): ${e.message}") + + if (attempts >= maxAttempts) { + logger.error("[FCM] ❌ 최종 실패: 알 수 없는 오류") + } + } } } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt index e26be41..0da7a69 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt @@ -65,7 +65,8 @@ class MemberController( userActionService.recordAction( memberId = response.memberId, - actionType = ActionType.SIGN_UP + actionType = ActionType.SIGN_UP, + pushToken = request.pushToken ) return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse) @@ -340,7 +341,7 @@ class MemberController( } val token = authHeader.substring(7) - val response = googleAuthService.authenticate(token, request.container, request.marketingPid) + val response = googleAuthService.authenticate(token, request.container, request.marketingPid, request.pushToken) if (!response.marketingPid.isNullOrBlank()) { trackingService.saveTrackingHistory( @@ -350,6 +351,12 @@ class MemberController( ) } + userActionService.recordAction( + memberId = response.memberId, + actionType = ActionType.SIGN_UP, + pushToken = request.pushToken + ) + return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse) } @@ -363,7 +370,7 @@ class MemberController( } val token = authHeader.substring(7) - val response = kakaoAuthService.authenticate(token, request.container, request.marketingPid) + val response = kakaoAuthService.authenticate(token, request.container, request.marketingPid, request.pushToken) if (!response.marketingPid.isNullOrBlank()) { trackingService.saveTrackingHistory( @@ -373,6 +380,12 @@ class MemberController( ) } + userActionService.recordAction( + memberId = response.memberId, + actionType = ActionType.SIGN_UP, + pushToken = request.pushToken + ) + return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt index 4308bc6..0564d82 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt @@ -125,6 +125,7 @@ class MemberService( gender = Gender.NONE, container = request.container ) + member.pushToken = request.pushToken if (!request.marketingPid.isNullOrBlank()) { member.activePid = request.marketingPid @@ -780,7 +781,12 @@ class MemberService( } @Transactional - fun findOrRegister(googleUserInfo: GoogleUserInfo, container: String, marketingPid: String?): Member { + fun findOrRegister( + googleUserInfo: GoogleUserInfo, + container: String, + marketingPid: String?, + pushToken: String? + ): Member { val findMember = repository.findByGoogleId(googleUserInfo.sub) if (findMember != null) { if (findMember.isActive) { @@ -810,6 +816,7 @@ class MemberService( provider = MemberProvider.GOOGLE, container = container ) + member.pushToken = pushToken if (!marketingPid.isNullOrBlank()) { member.activePid = marketingPid @@ -823,7 +830,12 @@ class MemberService( } @Transactional - fun findOrRegister(kakaoUserInfo: KakaoUserInfo, container: String, marketingPid: String?): Member { + fun findOrRegister( + kakaoUserInfo: KakaoUserInfo, + container: String, + marketingPid: String?, + pushToken: String? + ): Member { val findMember = repository.findByKakaoId(kakaoUserInfo.id) if (findMember != null) { if (findMember.isActive) { @@ -853,6 +865,7 @@ class MemberService( provider = MemberProvider.KAKAO, container = container ) + member.pushToken = pushToken if (!marketingPid.isNullOrBlank()) { member.activePid = marketingPid diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/login/LoginRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/login/LoginRequest.kt index da16dc4..0895b91 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/login/LoginRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/login/LoginRequest.kt @@ -7,4 +7,8 @@ data class LoginRequest( val isCreator: Boolean = false ) -data class SocialLoginRequest(val container: String, val marketingPid: String? = null) +data class SocialLoginRequest( + val container: String, + val pushToken: String? = null, + val marketingPid: String? = null +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/signUp/SignUpRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/signUp/SignUpRequest.kt index de4a6fa..03ccc0f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/signUp/SignUpRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/signUp/SignUpRequest.kt @@ -16,6 +16,7 @@ data class SignUpRequest( data class SignUpRequestV2( val email: String, val password: String, + val pushToken: String? = null, val marketingPid: String? = null, val isAgreeTermsOfService: Boolean, val isAgreePrivacyPolicy: Boolean, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleAuthService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleAuthService.kt index 495e269..0d39c74 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleAuthService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleAuthService.kt @@ -19,10 +19,15 @@ class GoogleAuthService( @Value("\${cloud.aws.cloud-front.host}") private val cloudFrontHost: String ) { - fun authenticate(idToken: String, container: String, marketingPid: String?): SocialLoginResponse { + fun authenticate( + idToken: String, + container: String, + marketingPid: String?, + pushToken: String? + ): SocialLoginResponse { val googleUserInfo = googleService.getUserInfo(idToken) ?: throw SodaException("구글 로그인을 하지 못했습니다. 다시 시도해 주세요") - val member = memberService.findOrRegister(googleUserInfo, container, marketingPid) + val member = memberService.findOrRegister(googleUserInfo, container, marketingPid, pushToken) val principal = MemberAdapter(member) val authToken = GoogleAuthenticationToken(idToken, principal.authorities) authToken.setPrincipal(principal) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoAuthService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoAuthService.kt index 49525bf..026aa79 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoAuthService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoAuthService.kt @@ -19,10 +19,15 @@ class KakaoAuthService( @Value("\${cloud.aws.cloud-front.host}") private val cloudFrontHost: String ) { - fun authenticate(accessToken: String, container: String, marketingPid: String?): SocialLoginResponse { + fun authenticate( + accessToken: String, + container: String, + marketingPid: String?, + pushToken: String? + ): SocialLoginResponse { val kakaoUserInfo = kakaoService.getUserInfo(accessToken) ?: throw SodaException("카카오 로그인을 하지 못했습니다. 다시 시도해 주세요") - val member = memberService.findOrRegister(kakaoUserInfo, container, marketingPid) + val member = memberService.findOrRegister(kakaoUserInfo, container, marketingPid, pushToken) val principal = MemberAdapter(member) val authToken = KakaoAuthenticationToken(accessToken, principal.authorities) authToken.setPrincipal(principal) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index 1128897..b3f0236 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.useraction import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kr.co.vividnext.sodalive.fcm.FcmService import kr.co.vividnext.sodalive.point.MemberPoint import kr.co.vividnext.sodalive.point.MemberPointRepository import kr.co.vividnext.sodalive.point.PointGrantLog @@ -16,12 +17,14 @@ class UserActionService( private val repository: UserActionLogRepository, private val policyRepository: PointRewardPolicyRepository, private val grantLogRepository: PointGrantLogRepository, - private val memberPointRepository: MemberPointRepository + private val memberPointRepository: MemberPointRepository, + + private val fcmService: FcmService ) { private val coroutineScope = CoroutineScope(Dispatchers.IO) - fun recordAction(memberId: Long, actionType: ActionType) { + fun recordAction(memberId: Long, actionType: ActionType, pushToken: String?) { coroutineScope.launch { val now = LocalDateTime.now() repository.save(UserActionLog(memberId, actionType)) @@ -56,6 +59,10 @@ class UserActionService( policyId = policy.id!! ) ) + + if (pushToken != null) { + fcmService.sendPointGranted(pushToken, policy.pointAmount) + } } } } From c1d4c1ff1d99798d3bc7300ee9f37b1ee2dde8a8 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Apr 2025 17:44:19 +0900 Subject: [PATCH 07/25] =?UTF-8?q?feat:=20=EA=B8=B0=EC=A1=B4=20=ED=91=B8?= =?UTF-8?q?=EC=8B=9C=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A0=84=EC=86=A1=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=97=90=20=EC=B5=9C=EB=8C=80=203=ED=9A=8C?= =?UTF-8?q?=20=EC=9E=AC=EC=8B=9C=EB=8F=84=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../co/vividnext/sodalive/fcm/FcmService.kt | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmService.kt index 1f96de1..7a51f63 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmService.kt @@ -29,8 +29,14 @@ class FcmService { creatorId: Long? = null, auditionId: Long? = null ) { - if (tokens.isNotEmpty()) { - logger.info("os: $container") + if (tokens.isEmpty()) return + logger.info("os: $container") + + var targets = tokens + val maxAttempts = 3 + var attempt = 1 + + while (attempt <= maxAttempts && targets.isNotEmpty()) { val multicastMessage = MulticastMessage.builder() .addAllTokens(tokens) @@ -85,8 +91,34 @@ class FcmService { } val response = FirebaseMessaging.getInstance().sendEachForMulticast(multicastMessage.build()) - logger.info("[FCM] ✅ 성공: ${response.successCount}") - logger.info("[FCM] ❌ 실패: ${response.failureCount}") + val failedTokens = mutableListOf() + + response.responses.forEachIndexed { index, res -> + if (!res.isSuccessful) { + val exception = res.exception + val token = targets[index] + + if (exception?.messagingErrorCode == MessagingErrorCode.UNREGISTERED) { + logger.error("[FCM] ❌ UNREGISTERED → $token") + // 필요 시 DB에서 삭제 + } else { + logger.error("[FCM] ❌ 실패: $token / ${exception?.messagingErrorCode}") + failedTokens.add(token) + } + } + } + + if (failedTokens.isEmpty()) { + logger.info("[FCM] ✅ 전체 전송 성공") + return + } + + targets = failedTokens + attempt++ + } + + if (targets.isNotEmpty()) { + logger.error("[FCM] ❌ 최종 실패 대상 ${targets.size}명 → $targets") } } From b9cb8ad4a83fbc76548ddb1e34a5b2e0f8bd3e8b Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Apr 2025 18:49:52 +0900 Subject: [PATCH 08/25] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EA=B2=B0=EC=A0=9C=20=EC=A1=B0=EA=B1=B4=20-=20=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EA=B2=B0=EC=A0=9C=EA=B0=80=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=9C=20=EC=BD=98=ED=85=90=EC=B8=A0=EB=A7=8C=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EA=B2=B0=EC=A0=9C=EB=A5=BC=20?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/co/vividnext/sodalive/content/order/OrderService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt index 81e460f..2e0f446 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt @@ -43,7 +43,7 @@ class OrderService( orderContent(orderType, content, member) } - val usedPoint = if (order.type == OrderType.RENTAL) { + val usedPoint = if (order.type == OrderType.RENTAL && content.isPointAvailable) { pointUsageService.usePoint(member.id!!, order.can) } else { 0 From 9e2d031b5dba4dab562c8ed96273fb6e24a135db Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Apr 2025 19:39:07 +0900 Subject: [PATCH 09/25] =?UTF-8?q?fix:=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20-=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=20=EA=B0=80=EB=8A=A5=20=EC=97=AC=EB=B6=80?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/co/vividnext/sodalive/content/AudioContentService.kt | 1 + .../co/vividnext/sodalive/content/CreateAudioContentRequest.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt index 7717c1e..bf3128e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt @@ -214,6 +214,7 @@ class AudioContentService( purchaseOption = purchaseOption, isGeneratePreview = request.isGeneratePreview, isOnlyRental = isOnlyRental, + isPointAvailable = request.isPointAvailable, isCommentAvailable = request.isCommentAvailable, isFullDetailVisible = isFullDetailVisible ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/CreateAudioContentRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/CreateAudioContentRequest.kt index 08ae03c..2eacb00 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/CreateAudioContentRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/CreateAudioContentRequest.kt @@ -13,6 +13,7 @@ data class CreateAudioContentRequest( val isAdult: Boolean = false, val isGeneratePreview: Boolean = false, val isOnlyRental: Boolean = false, + val isPointAvailable: Boolean = false, val isCommentAvailable: Boolean = false, val isFullDetailVisible: Boolean = true, val previewStartTime: String? = null, From e759f62b5f3b19ea406c67be04420753d7b3ccc1 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Apr 2025 21:07:16 +0900 Subject: [PATCH 10/25] =?UTF-8?q?fix:=20=ED=81=AC=EB=A6=AC=EC=97=90?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20-=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=82=AC=EC=9A=A9=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=20=EC=97=AC=EB=B6=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../creator/admin/content/CreatorAdminContentRepository.kt | 1 + .../creator/admin/content/GetCreatorAdminContentListResponse.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt index 4a0a3da..519a969 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt @@ -84,6 +84,7 @@ class CreatorAdminAudioContentQueryRepositoryImpl( audioContent.limited, audioContent.remaining, audioContent.isAdult, + audioContent.isPointAvailable, audioContent.isCommentAvailable, audioContent.duration, audioContent.content, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/GetCreatorAdminContentListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/GetCreatorAdminContentListResponse.kt index 5176950..1975a38 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/GetCreatorAdminContentListResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/GetCreatorAdminContentListResponse.kt @@ -18,6 +18,7 @@ data class GetCreatorAdminContentListItem @QueryProjection constructor( val totalContentCount: Int?, val remainingContentCount: Int?, val isAdult: Boolean, + val isPointAvailable: Boolean, val isCommentAvailable: Boolean, val remainingTime: String, var contentUrl: String, From 761d56f4bddf0f718b8393af85f7caceb38fa2a8 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Apr 2025 21:19:47 +0900 Subject: [PATCH 11/25] =?UTF-8?q?fix:=20=ED=81=AC=EB=A6=AC=EC=97=90?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EC=88=98=EC=A0=95=20-=20=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EC=82=AC=EC=9A=A9=20=EA=B0=80=EB=8A=A5=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../creator/admin/content/CreatorAdminContentService.kt | 4 ++++ .../creator/admin/content/UpdateCreatorAdminContentRequest.kt | 1 + 2 files changed, 5 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentService.kt index a99972c..c5091c8 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentService.kt @@ -131,6 +131,10 @@ class CreatorAdminContentService( audioContent.isAdult = request.isAdult } + if (request.isPointAvailable != null) { + audioContent.isPointAvailable = request.isPointAvailable + } + if (request.isCommentAvailable != null) { audioContent.isCommentAvailable = request.isCommentAvailable } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/UpdateCreatorAdminContentRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/UpdateCreatorAdminContentRequest.kt index 3360562..8eeda93 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/UpdateCreatorAdminContentRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/UpdateCreatorAdminContentRequest.kt @@ -7,5 +7,6 @@ data class UpdateCreatorAdminContentRequest( val price: Int?, val isAdult: Boolean?, val isActive: Boolean?, + val isPointAvailable: Boolean?, val isCommentAvailable: Boolean? ) From a70b5d89ec5fb09f92f0950d2be829db2a849cb6 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Apr 2025 21:54:42 +0900 Subject: [PATCH 12/25] =?UTF-8?q?fix:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A0=95=EC=B1=85=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20-=20=EC=A0=84=EC=B2=B4=20=EA=B0=9C?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...nse.kt => GetPointRewardPolicyListResponse.kt} | 7 ++++++- .../sodalive/admin/point/PointPolicyRepository.kt | 15 ++++++++++++--- .../sodalive/admin/point/PointPolicyService.kt | 7 +++++-- 3 files changed, 23 insertions(+), 6 deletions(-) rename src/main/kotlin/kr/co/vividnext/sodalive/admin/point/{GetPointRewardPolicyResponse.kt => GetPointRewardPolicyListResponse.kt} (66%) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyListResponse.kt similarity index 66% rename from src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyResponse.kt rename to src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyListResponse.kt index 4e8203d..33dd64e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyListResponse.kt @@ -3,7 +3,12 @@ package kr.co.vividnext.sodalive.admin.point import com.querydsl.core.annotations.QueryProjection import kr.co.vividnext.sodalive.useraction.ActionType -data class GetPointRewardPolicyResponse @QueryProjection constructor( +data class GetPointRewardPolicyListResponse( + val totalCount: Int, + val items: List +) + +data class GetPointRewardPolicyListItem @QueryProjection constructor( val id: Long, val title: String, val actionType: ActionType, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt index 3c644d5..d439d4d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt @@ -12,16 +12,25 @@ import java.time.LocalDateTime interface PointPolicyRepository : JpaRepository, PointPolicyQueryRepository interface PointPolicyQueryRepository { - fun getAll(offset: Long, limit: Long): List + fun getTotalCount(): Int + fun getAll(offset: Long, limit: Long): List } class PointPolicyQueryRepositoryImpl( private val queryFactory: JPAQueryFactory ) : PointPolicyQueryRepository { - override fun getAll(offset: Long, limit: Long): List { + override fun getTotalCount(): Int { + return queryFactory + .select(pointRewardPolicy.id) + .from(pointRewardPolicy) + .fetch() + .size + } + + override fun getAll(offset: Long, limit: Long): List { return queryFactory .select( - QGetPointRewardPolicyResponse( + QGetPointRewardPolicyListItem( pointRewardPolicy.id, pointRewardPolicy.title, pointRewardPolicy.actionType, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt index d5b8c54..1616b84 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt @@ -9,8 +9,11 @@ import java.time.format.DateTimeFormatter @Service class PointPolicyService(private val repository: PointPolicyRepository) { - fun getAll(offset: Long, limit: Long): List { - return repository.getAll(offset, limit) + fun getAll(offset: Long, limit: Long): GetPointRewardPolicyListResponse { + val totalCount = repository.getTotalCount() + val items = repository.getAll(offset, limit) + + return GetPointRewardPolicyListResponse(totalCount, items) } fun create(request: CreatePointRewardPolicyRequest) { From 24e62c18851695e18f146b0cb12fea4efaa90037 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Apr 2025 22:10:38 +0900 Subject: [PATCH 13/25] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EC=A1=B0=ED=9A=8C=20Query=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20-=20where=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=EC=97=90=20=EB=B6=88=EC=99=84=EC=A0=84=ED=95=9C=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=EB=AC=B8=EC=9D=B4=20=EB=93=A4=EC=96=B4=EC=9E=88?= =?UTF-8?q?=EB=8D=98=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/point/PointRewardPolicyRepository.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt index 9587517..6463d5d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt @@ -22,11 +22,13 @@ class PointRewardPolicyQueryRepositoryImpl( return queryFactory .selectFrom(pointRewardPolicy) .where( - pointRewardPolicy.isActive, - pointRewardPolicy.actionType.eq(actionType), - pointRewardPolicy.startDate.loe(nowDateTime), - pointRewardPolicy.endDate.goe(nowDateTime) - .or(pointRewardPolicy.endDate.isNull) + pointRewardPolicy.isActive + .and(pointRewardPolicy.actionType.eq(actionType)) + .and(pointRewardPolicy.startDate.loe(nowDateTime)) + .and( + pointRewardPolicy.endDate.goe(nowDateTime) + .or(pointRewardPolicy.endDate.isNull) + ) ) .orderBy(pointRewardPolicy.endDate.asc()) .fetchFirst() From 57adfec490f5255a87f52b7ee5de95e81743126d Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Apr 2025 22:30:12 +0900 Subject: [PATCH 14/25] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EC=A1=B0=ED=9A=8C=20Query=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20-=20where=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=EC=97=90=20=EB=B6=88=EC=99=84=EC=A0=84=ED=95=9C=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=EB=AC=B8=EC=9D=B4=20=EB=93=A4=EC=96=B4=EC=9E=88?= =?UTF-8?q?=EB=8D=98=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../co/vividnext/sodalive/point/PointRewardPolicyRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt index 6463d5d..2b3ac5e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt @@ -30,7 +30,7 @@ class PointRewardPolicyQueryRepositoryImpl( .or(pointRewardPolicy.endDate.isNull) ) ) - .orderBy(pointRewardPolicy.endDate.asc()) + .orderBy(pointRewardPolicy.id.desc()) .fetchFirst() } } From 775391f59086350b1b97d8e6e542330e5aaa53ae Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Apr 2025 22:42:07 +0900 Subject: [PATCH 15/25] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EC=A1=B0=ED=9A=8C=20Query=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20-=20where=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=EC=97=90=20=EB=B6=88=EC=99=84=EC=A0=84=ED=95=9C=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=EB=AC=B8=EC=9D=B4=20=EB=93=A4=EC=96=B4=EC=9E=88?= =?UTF-8?q?=EB=8D=98=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vividnext/sodalive/point/PointRewardPolicyRepository.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt index 2b3ac5e..c9e8fcb 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicyRepository.kt @@ -22,7 +22,7 @@ class PointRewardPolicyQueryRepositoryImpl( return queryFactory .selectFrom(pointRewardPolicy) .where( - pointRewardPolicy.isActive + pointRewardPolicy.isActive.isTrue .and(pointRewardPolicy.actionType.eq(actionType)) .and(pointRewardPolicy.startDate.loe(nowDateTime)) .and( @@ -30,7 +30,7 @@ class PointRewardPolicyQueryRepositoryImpl( .or(pointRewardPolicy.endDate.isNull) ) ) - .orderBy(pointRewardPolicy.id.desc()) + .orderBy(pointRewardPolicy.endDate.asc()) .fetchFirst() } } From 7c3b7cffc2a949db091143953107d0cce6fbb885 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Apr 2025 23:39:48 +0900 Subject: [PATCH 16/25] =?UTF-8?q?fix:=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EC=A3=BC=EB=AC=B8=20-=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EA=B2=B0=EC=A0=9C=20=ED=9B=84=20=EC=B6=94=EA=B0=80=20=EA=B2=B0?= =?UTF-8?q?=EC=A0=9C=EB=A5=BC=20=ED=95=B4=EC=95=BC=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=BA=94=EC=9D=B4=20=EB=82=A8=EC=95=84=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=EC=97=90=EB=A7=8C=20=EC=BA=94=EC=9D=84=20?= =?UTF-8?q?=EA=B2=B0=EC=A0=9C=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(=EB=82=A8=EC=95=84=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EC=BA=94=EC=9D=B4=20=EC=97=86=EB=8A=94=EB=8D=B0=20=EA=B2=B0?= =?UTF-8?q?=EC=A0=9C=20=EC=B2=98=EB=A6=AC=EA=B0=80=20=EB=90=98=EC=84=9C=20?= =?UTF-8?q?0=EC=BA=94=EC=9C=BC=EB=A1=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EA=B0=80=20=EC=8C=93=EC=9D=B4=EB=8A=94=20=EA=B2=83=20=EB=B0=A9?= =?UTF-8?q?=EC=A7=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/content/order/OrderService.kt | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt index 2e0f446..255da15 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt @@ -50,13 +50,20 @@ class OrderService( } order.point = usedPoint - canPaymentService.spendCan( - memberId = member.id!!, - needCan = order.can - (usedPoint / 10), - canUsage = CanUsage.ORDER_CONTENT, - order = order, - container = container - ) + val remainingCan = order.can - (usedPoint / 10) + if (order.type == OrderType.RENTAL && content.isPointAvailable && usedPoint > 0) { + order.can = remainingCan + } + + if (remainingCan > 0) { + canPaymentService.spendCan( + memberId = member.id!!, + needCan = remainingCan, + canUsage = CanUsage.ORDER_CONTENT, + order = order, + container = container + ) + } } private fun orderContent(orderType: OrderType, content: AudioContent, member: Member): Order { From e2daff64633caeb7285a8d12e51849b551c68715 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Apr 2025 00:55:24 +0900 Subject: [PATCH 17/25] =?UTF-8?q?feat:=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EC=A0=95=EC=82=B0=20-=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=EB=A5=BC?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=ED=95=9C=20=EC=A3=BC=EB=AC=B8=EA=B3=BC=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EC=9D=80=20?= =?UTF-8?q?=EC=A3=BC=EB=AC=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminCalculateQueryRepository.kt | 21 ++++++++++++++++++- .../calculate/GetCalculateContentQueryData.kt | 6 +++++- .../GetCumulativeSalesByContentResponse.kt | 6 +++++- .../CreatorAdminCalculateQueryRepository.kt | 21 ++++++++++++++++++- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/AdminCalculateQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/AdminCalculateQueryRepository.kt index 47c243b..e7b6e54 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/AdminCalculateQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/AdminCalculateQueryRepository.kt @@ -1,5 +1,6 @@ package kr.co.vividnext.sodalive.admin.calculate +import com.querydsl.core.types.dsl.CaseBuilder import com.querydsl.core.types.dsl.DateTimePath import com.querydsl.core.types.dsl.Expressions import com.querydsl.core.types.dsl.StringTemplate @@ -51,6 +52,10 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) { fun getCalculateContentList(startDate: LocalDateTime, endDate: LocalDateTime): List { val orderFormattedDate = getFormattedDate(order.createdAt) + val pointGroup = CaseBuilder() + .`when`(order.point.loe(0)).then(0) + .otherwise(1) + return queryFactory .select( QGetCalculateContentQueryData( @@ -62,6 +67,7 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) { order.can, order.id.count(), order.can.sum(), + order.point.sum(), creatorSettlementRatio.contentSettlementRatio ) ) @@ -80,6 +86,7 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) { order.type, orderFormattedDate, order.can, + pointGroup, creatorSettlementRatio.contentSettlementRatio ) .orderBy(member.id.desc(), orderFormattedDate.desc(), audioContent.id.asc()) @@ -113,6 +120,10 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) { } fun getCumulativeSalesByContent(offset: Long, limit: Long): List { + val pointGroup = CaseBuilder() + .`when`(order.point.loe(0)).then(0) + .otherwise(1) + return queryFactory .select( QGetCumulativeSalesByContentQueryData( @@ -123,6 +134,7 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) { order.can, order.id.count(), order.can.sum(), + order.point.sum(), creatorSettlementRatio.contentSettlementRatio ) ) @@ -132,7 +144,14 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) { .leftJoin(creatorSettlementRatio) .on(member.id.eq(creatorSettlementRatio.member.id)) .where(order.isActive.isTrue) - .groupBy(member.id, audioContent.id, order.type, order.can) + .groupBy( + member.id, + audioContent.id, + order.type, + order.can, + pointGroup, + creatorSettlementRatio.contentSettlementRatio + ) .offset(offset) .limit(limit) .orderBy(member.id.desc(), audioContent.id.desc()) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/GetCalculateContentQueryData.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/GetCalculateContentQueryData.kt index cf0a0c6..b3a60a4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/GetCalculateContentQueryData.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/GetCalculateContentQueryData.kt @@ -22,11 +22,15 @@ data class GetCalculateContentQueryData @QueryProjection constructor( val numberOfPeople: Long, // 합계 val totalCan: Int, + // 포인트 + val totalPoint: Int, // 정산비율 val settlementRatio: Int? ) { fun toGetCalculateContentResponse(): GetCalculateContentResponse { - val orderTypeStr = if (orderType == OrderType.RENTAL) { + val orderTypeStr = if (totalPoint > 0) { + "포인트" + } else if (orderType == OrderType.RENTAL) { "대여" } else { "소장" diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/GetCumulativeSalesByContentResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/GetCumulativeSalesByContentResponse.kt index 5c49858..a003a78 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/GetCumulativeSalesByContentResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/GetCumulativeSalesByContentResponse.kt @@ -21,11 +21,15 @@ data class GetCumulativeSalesByContentQueryData @QueryProjection constructor( val numberOfPeople: Long, // 합계 val totalCan: Int, + // 포인트 + val totalPoint: Int, // 정산비율 val settlementRatio: Int? ) { fun toCumulativeSalesByContentItem(): CumulativeSalesByContentItem { - val orderTypeStr = if (orderType == OrderType.RENTAL) { + val orderTypeStr = if (totalPoint > 0) { + "포인트" + } else if (orderType == OrderType.RENTAL) { "대여" } else { "소장" diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/calculate/CreatorAdminCalculateQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/calculate/CreatorAdminCalculateQueryRepository.kt index bae92bc..f9b4289 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/calculate/CreatorAdminCalculateQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/calculate/CreatorAdminCalculateQueryRepository.kt @@ -1,5 +1,6 @@ package kr.co.vividnext.sodalive.creator.admin.calculate +import com.querydsl.core.types.dsl.CaseBuilder import com.querydsl.core.types.dsl.DateTimePath import com.querydsl.core.types.dsl.Expressions import com.querydsl.core.types.dsl.StringTemplate @@ -95,6 +96,10 @@ class CreatorAdminCalculateQueryRepository(private val queryFactory: JPAQueryFac limit: Long ): List { val orderFormattedDate = getFormattedDate(order.createdAt) + val pointGroup = CaseBuilder() + .`when`(order.point.loe(0)).then(0) + .otherwise(1) + return queryFactory .select( QGetCalculateContentQueryData( @@ -106,6 +111,7 @@ class CreatorAdminCalculateQueryRepository(private val queryFactory: JPAQueryFac order.can, order.id.count(), order.can.sum(), + order.point.sum(), creatorSettlementRatio.contentSettlementRatio ) ) @@ -125,6 +131,7 @@ class CreatorAdminCalculateQueryRepository(private val queryFactory: JPAQueryFac order.type, orderFormattedDate, order.can, + pointGroup, creatorSettlementRatio.contentSettlementRatio ) .offset(offset) @@ -167,6 +174,10 @@ class CreatorAdminCalculateQueryRepository(private val queryFactory: JPAQueryFac offset: Long, limit: Long ): List { + val pointGroup = CaseBuilder() + .`when`(order.point.loe(0)).then(0) + .otherwise(1) + return queryFactory .select( QGetCumulativeSalesByContentQueryData( @@ -177,6 +188,7 @@ class CreatorAdminCalculateQueryRepository(private val queryFactory: JPAQueryFac order.can, order.id.count(), order.can.sum(), + order.point.sum(), creatorSettlementRatio.contentSettlementRatio ) ) @@ -189,7 +201,14 @@ class CreatorAdminCalculateQueryRepository(private val queryFactory: JPAQueryFac audioContent.member.id.eq(memberId) .and(order.isActive.isTrue) ) - .groupBy(member.id, audioContent.id, order.type, order.can) + .groupBy( + member.id, + audioContent.id, + order.type, + order.can, + pointGroup, + creatorSettlementRatio.contentSettlementRatio + ) .offset(offset) .limit(limit) .orderBy(member.id.desc(), audioContent.id.desc()) From 58d066af0a617d0bf14c4b58a04d3579cc9f3ab0 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Apr 2025 14:45:13 +0900 Subject: [PATCH 18/25] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20=ED=96=89?= =?UTF-8?q?=EB=8F=99=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20-=20=EB=B3=B8?= =?UTF-8?q?=EC=9D=B8=EC=9D=B8=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/member/auth/AuthController.kt | 13 ++++++++++++- .../co/vividnext/sodalive/useraction/ActionType.kt | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt index ac823f2..173a186 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt @@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.member.auth import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.useraction.ActionType +import kr.co.vividnext.sodalive.useraction.UserActionService import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -11,7 +13,10 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/auth") -class AuthController(private val service: AuthService) { +class AuthController( + private val service: AuthService, + private val userActionService: UserActionService +) { @PostMapping fun authVerify( @RequestBody request: AuthVerifyRequest, @@ -26,6 +31,12 @@ class AuthController(private val service: AuthService) { throw SodaException("운영정책을 위반하여 이용을 제한합니다.") } + userActionService.recordAction( + memberId = member.id!!, + actionType = ActionType.USER_AUTHENTICATION, + pushToken = member.pushToken + ) + ApiResponse.ok(service.authenticate(authenticateData, member.id!!)) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/ActionType.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/ActionType.kt index 5b093f3..a9365c4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/ActionType.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/ActionType.kt @@ -1,5 +1,6 @@ package kr.co.vividnext.sodalive.useraction enum class ActionType(val displayName: String) { - SIGN_UP("회원가입") + SIGN_UP("회원가입"), + USER_AUTHENTICATION("본인인증") } From cb7917dc26b3dd1f8789b266bdc4b55a3cc66268 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Apr 2025 14:57:33 +0900 Subject: [PATCH 19/25] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EB=93=B1=EB=A1=9D=20-=20request=EC=97=90?= =?UTF-8?q?=20=ED=99=9C=EC=84=B1=ED=99=94=20=EC=97=AC=EB=B6=80=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/admin/point/CreatePointRewardPolicyRequest.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt index a466a32..ff7a531 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt @@ -12,8 +12,7 @@ data class CreatePointRewardPolicyRequest( val threshold: Int, val pointAmount: Int, val startDate: String, - val endDate: String, - val isActive: Boolean + val endDate: String ) { fun toEntity(): PointRewardPolicy { val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") @@ -31,7 +30,7 @@ data class CreatePointRewardPolicyRequest( .atZone(ZoneId.of("Asia/Seoul")) .withZoneSameInstant(ZoneId.of("UTC")) .toLocalDateTime(), - isActive = isActive + isActive = true ) } } From fa98138541289fd2df7ab23c3eeba1cff9362add Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Apr 2025 16:55:58 +0900 Subject: [PATCH 20/25] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EC=83=9D=EC=84=B1=20-=20endDate=EA=B0=80?= =?UTF-8?q?=20=EB=B9=88=EC=B9=B8=EC=9D=B4=EB=A9=B4=20null=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/point/CreatePointRewardPolicyRequest.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt index ff7a531..4c2cf73 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt @@ -26,10 +26,14 @@ data class CreatePointRewardPolicyRequest( .atZone(ZoneId.of("Asia/Seoul")) .withZoneSameInstant(ZoneId.of("UTC")) .toLocalDateTime(), - endDate = LocalDateTime.parse(endDate, dateTimeFormatter).withSecond(59) - .atZone(ZoneId.of("Asia/Seoul")) - .withZoneSameInstant(ZoneId.of("UTC")) - .toLocalDateTime(), + endDate = if (endDate.isNotBlank()) { + LocalDateTime.parse(endDate, dateTimeFormatter).withSecond(59) + .atZone(ZoneId.of("Asia/Seoul")) + .withZoneSameInstant(ZoneId.of("UTC")) + .toLocalDateTime() + } else { + null + }, isActive = true ) } From 6ff044e4ab35165d5198ef64c9167ec55443e77b Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Apr 2025 17:09:57 +0900 Subject: [PATCH 21/25] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EC=A1=B0=ED=9A=8C=20-=20date=EA=B0=80=20n?= =?UTF-8?q?ull=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20=EB=B9=88=EC=B9=B8=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/admin/point/PointPolicyRepository.kt | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt index d439d4d..2cd63db 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt @@ -50,15 +50,8 @@ class PointPolicyQueryRepositoryImpl( private fun getFormattedDate(dateTimePath: DateTimePath): StringTemplate { return Expressions.stringTemplate( - "DATE_FORMAT({0}, {1})", - Expressions.dateTimeTemplate( - LocalDateTime::class.java, - "CONVERT_TZ({0},{1},{2})", - dateTimePath, - "UTC", - "Asia/Seoul" - ), - "%Y-%m-%d %H:%i" + "COALESCE(DATE_FORMAT(CONVERT_TZ({0}, 'UTC', 'Asia/Seoul'), '%Y-%m-%d %H:%i'), '')", + dateTimePath ) } } From ca704f38b9649a72536a9d2d3730f5dccf345ef3 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Apr 2025 17:29:08 +0900 Subject: [PATCH 22/25] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EC=88=98=EC=A0=95=20-=20@Transactional=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt index 1616b84..c179114 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyService.kt @@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.admin.point import kr.co.vividnext.sodalive.common.SodaException import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional import java.time.LocalDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter @@ -16,11 +17,13 @@ class PointPolicyService(private val repository: PointPolicyRepository) { return GetPointRewardPolicyListResponse(totalCount, items) } + @Transactional fun create(request: CreatePointRewardPolicyRequest) { val pointPolicy = request.toEntity() repository.save(pointPolicy) } + @Transactional fun update(id: Long, request: ModifyPointRewardPolicyRequest) { val pointPolicy = repository.findByIdOrNull(id) ?: throw SodaException("잘못된 접근입니다.") From 3940282ed85c934725c39595c07ea8a531e3ad50 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Apr 2025 18:26:47 +0900 Subject: [PATCH 23/25] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20-=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/co/vividnext/sodalive/member/MemberService.kt | 8 ++++++++ .../co/vividnext/sodalive/member/myPage/MyPageResponse.kt | 1 + 2 files changed, 9 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt index 0564d82..5a4f0e3 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt @@ -42,6 +42,7 @@ import kr.co.vividnext.sodalive.member.stipulation.StipulationRepository import kr.co.vividnext.sodalive.member.tag.MemberCreatorTag import kr.co.vividnext.sodalive.member.tag.MemberTagRepository import kr.co.vividnext.sodalive.member.token.MemberTokenRepository +import kr.co.vividnext.sodalive.point.MemberPointRepository import kr.co.vividnext.sodalive.utils.generateFileName import kr.co.vividnext.sodalive.utils.generatePassword import org.springframework.beans.factory.annotation.Value @@ -77,6 +78,7 @@ class MemberService( private val memberTagRepository: MemberTagRepository, private val liveReservationRepository: LiveReservationRepository, private val chargeRepository: ChargeRepository, + private val memberPointRepository: MemberPointRepository, private val orderService: OrderService, private val emailService: SendEmailService, @@ -262,6 +264,11 @@ class MemberService( limit = 4 ) + val totalPoint = memberPointRepository.findByMemberIdAndExpiresAtAfterOrderByExpiresAtAsc( + memberId = member.id!!, + expiresAt = LocalDateTime.now() + ).sumOf { it.point } + return MyPageResponse( nickname = member.nickname, profileUrl = if (member.profileImage != null) { @@ -271,6 +278,7 @@ class MemberService( }, chargeCan = member.getChargeCan(container = container), rewardCan = member.getRewardCan(container = container), + point = totalPoint, youtubeUrl = member.youtubeUrl, instagramUrl = member.instagramUrl, websiteUrl = member.websiteUrl, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/myPage/MyPageResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/myPage/MyPageResponse.kt index 54d7a33..5bf0481 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/myPage/MyPageResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/myPage/MyPageResponse.kt @@ -7,6 +7,7 @@ data class MyPageResponse( val profileUrl: String, val chargeCan: Int, val rewardCan: Int, + val point: Int, val youtubeUrl: String?, val instagramUrl: String?, val websiteUrl: String? = null, From 8a937f01a450109a636de5268c1ae06e9422a879 Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 24 Apr 2025 10:50:14 +0900 Subject: [PATCH 24/25] =?UTF-8?q?feat:=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20-=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EA=B0=80=EB=8A=A5=20=EC=97=AC=EB=B6=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/co/vividnext/sodalive/content/AudioContentService.kt | 3 ++- .../sodalive/content/GetAudioContentDetailResponse.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt index bf3128e..a8b25f4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt @@ -708,7 +708,8 @@ class AudioContentService( ), previousContent = previousContent, nextContent = nextContent, - buyerList = buyerList + buyerList = buyerList, + isAvailableUsePoint = audioContent.isPointAvailable ) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/GetAudioContentDetailResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/GetAudioContentDetailResponse.kt index 4eba473..9149ce2 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/GetAudioContentDetailResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/GetAudioContentDetailResponse.kt @@ -38,7 +38,8 @@ data class GetAudioContentDetailResponse( val creator: AudioContentCreator, val previousContent: OtherContentResponse?, val nextContent: OtherContentResponse?, - val buyerList: List + val buyerList: List, + val isAvailableUsePoint: Boolean ) data class OtherContentResponse @QueryProjection constructor( From 27c5b991cf8c55e7a7c11c206aa7af7e4d49e3cb Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 24 Apr 2025 11:37:11 +0900 Subject: [PATCH 25/25] =?UTF-8?q?fix:=20=EC=98=A4=EB=94=94=EC=85=98=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=20=EB=82=B4=EC=97=AD=20-=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=ED=95=9C=20=EC=82=AC=EB=9E=8C=EC=9D=80=20=EB=B3=B4?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../audition/applicant/AuditionApplicantRepository.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantRepository.kt index 92af3f3..7d56c8a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantRepository.kt @@ -49,10 +49,12 @@ class AuditionApplicantQueryRepositoryImpl( return queryFactory .select(auditionApplicant.id) .from(auditionApplicant) + .innerJoin(auditionApplicant.member, member) .innerJoin(auditionApplicant.role, auditionRole) .where( auditionRole.id.eq(auditionRoleId), - auditionApplicant.isActive.isTrue + auditionApplicant.isActive.isTrue, + member.isActive.isTrue ) .fetch() .size @@ -87,7 +89,8 @@ class AuditionApplicantQueryRepositoryImpl( .leftJoin(auditionVote).on(auditionApplicant.id.eq(auditionVote.applicant.id)) .where( auditionRole.id.eq(auditionRoleId), - auditionApplicant.isActive.isTrue + auditionApplicant.isActive.isTrue, + member.isActive.isTrue ) .groupBy(auditionApplicant.id) .orderBy(orderBy)