Merge pull request 'test' (#358) from test into main

Reviewed-on: #358
This commit is contained in:
2025-11-10 06:53:41 +00:00
10 changed files with 103 additions and 18 deletions

View File

@@ -1,7 +1,7 @@
package kr.co.vividnext.sodalive.admin.can package kr.co.vividnext.sodalive.admin.can
data class AdminCanChargeRequest( data class AdminCanChargeRequest(
val memberId: Long, val memberIds: List<Long>,
val method: String, val method: String,
val can: Int val can: Int
) )

View File

@@ -40,12 +40,16 @@ class AdminCanService(
@Transactional @Transactional
fun charge(request: AdminCanChargeRequest) { fun charge(request: AdminCanChargeRequest) {
val member = memberRepository.findByIdOrNull(request.memberId)
?: throw SodaException("잘못된 회원번호 입니다.")
if (request.can <= 0) throw SodaException("1 캔 이상 입력하세요.") if (request.can <= 0) throw SodaException("1 캔 이상 입력하세요.")
if (request.method.isBlank()) throw SodaException("기록내용을 입력하세요.") if (request.method.isBlank()) throw SodaException("기록내용을 입력하세요.")
val ids = request.memberIds.distinct()
if (ids.isEmpty()) throw SodaException("회원번호를 입력하세요.")
val members = memberRepository.findAllById(ids).toList()
if (members.size != ids.size) throw SodaException("잘못된 회원번호 입니다.")
members.forEach { member ->
val charge = Charge(0, request.can, status = ChargeStatus.ADMIN) val charge = Charge(0, request.can, status = ChargeStatus.ADMIN)
charge.title = "${request.can.moneyFormat()}" charge.title = "${request.can.moneyFormat()}"
charge.member = member charge.member = member
@@ -58,4 +62,5 @@ class AdminCanService(
member.pgRewardCan += charge.rewardCan member.pgRewardCan += charge.rewardCan
} }
}
} }

View File

@@ -36,6 +36,12 @@ class AdminMemberController(private val service: AdminMemberService) {
pageable: Pageable pageable: Pageable
) = ApiResponse.ok(service.searchMember(searchWord, pageable)) ) = ApiResponse.ok(service.searchMember(searchWord, pageable))
@GetMapping("/search-by-nickname")
fun searchMemberByNickname(
@RequestParam(value = "search_word") searchWord: String,
@RequestParam(value = "size", required = false) size: Int?
) = ApiResponse.ok(service.searchMemberByNickname(searchWord = searchWord, size = size ?: 20))
@GetMapping("/creator/all/list") @GetMapping("/creator/all/list")
fun getCreatorAllList() = ApiResponse.ok(service.getCreatorAllList()) fun getCreatorAllList() = ApiResponse.ok(service.getCreatorAllList())

View File

@@ -16,6 +16,7 @@ interface AdminMemberQueryRepository {
fun searchMemberTotalCount(searchWord: String, role: MemberRole? = null): Int fun searchMemberTotalCount(searchWord: String, role: MemberRole? = null): Int
fun getCreatorAllList(): List<GetAdminCreatorAllListResponse> fun getCreatorAllList(): List<GetAdminCreatorAllListResponse>
fun findByIdAndActive(memberId: Long): Member? fun findByIdAndActive(memberId: Long): Member?
fun searchMemberByNickname(searchWord: String, limit: Long = 20): List<AdminSimpleMemberResponse>
} }
class AdminMemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : AdminMemberQueryRepository { class AdminMemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : AdminMemberQueryRepository {
@@ -121,4 +122,22 @@ class AdminMemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
.orderBy(member.id.desc()) .orderBy(member.id.desc())
.fetchFirst() .fetchFirst()
} }
override fun searchMemberByNickname(searchWord: String, limit: Long): List<AdminSimpleMemberResponse> {
return queryFactory
.select(
QAdminSimpleMemberResponse(
member.id,
member.nickname
)
)
.from(member)
.where(
member.nickname.contains(searchWord)
.and(member.isActive.isTrue)
)
.orderBy(member.id.desc())
.limit(limit)
.fetch()
}
} }

View File

@@ -145,6 +145,12 @@ class AdminMemberService(
return repository.getCreatorAllList() return repository.getCreatorAllList()
} }
fun searchMemberByNickname(searchWord: String, size: Int = 20): List<AdminSimpleMemberResponse> {
if (searchWord.length < 2) throw SodaException("2글자 이상 입력하세요.")
val limit = if (size <= 0) 20 else size
return repository.searchMemberByNickname(searchWord = searchWord, limit = limit.toLong())
}
@Transactional @Transactional
fun resetPassword(request: ResetPasswordRequest) { fun resetPassword(request: ResetPasswordRequest) {
val member = repository.findByIdAndActive(memberId = request.memberId) val member = repository.findByIdAndActive(memberId = request.memberId)

View File

@@ -0,0 +1,12 @@
package kr.co.vividnext.sodalive.admin.member
import com.querydsl.core.annotations.QueryProjection
/**
* 관리자용 간단 회원 응답 DTO
* 닉네임 검색 결과로 사용되며 charge 등에서 memberId 선택에 활용된다.
*/
data class AdminSimpleMemberResponse @QueryProjection constructor(
val id: Long,
val nickname: String
)

View File

@@ -26,5 +26,6 @@ data class GetHomeResponse(
val contentRanking: List<GetAudioContentRankingItem>, val contentRanking: List<GetAudioContentRankingItem>,
val recommendChannelList: List<RecommendChannelResponse>, val recommendChannelList: List<RecommendChannelResponse>,
val freeContentList: List<AudioContentMainItem>, val freeContentList: List<AudioContentMainItem>,
val pointAvailableContentList: List<AudioContentMainItem>,
val curationList: List<GetContentCurationResponse> val curationList: List<GetContentCurationResponse>
) )

View File

@@ -165,7 +165,24 @@ class HomeService(
), ),
contentType = contentType, contentType = contentType,
isFree = true, isFree = true,
isAdult = isAdult isAdult = isAdult,
orderByRandom = true
).filter {
if (memberId != null) {
!memberService.isBlocked(blockedMemberId = memberId, memberId = it.creatorId)
} else {
true
}
}
// 포인트 사용가능 콘텐츠 리스트 - 랜덤으로 가져오기 (DB에서 isPointAvailable 조건 적용)
val pointAvailableContentList = contentService.getLatestContentByTheme(
theme = emptyList(),
contentType = contentType,
isFree = false,
isAdult = isAdult,
orderByRandom = true,
isPointAvailableOnly = true
).filter { ).filter {
if (memberId != null) { if (memberId != null) {
!memberService.isBlocked(blockedMemberId = memberId, memberId = it.creatorId) !memberService.isBlocked(blockedMemberId = memberId, memberId = it.creatorId)
@@ -195,6 +212,7 @@ class HomeService(
contentRanking = contentRanking, contentRanking = contentRanking,
recommendChannelList = recommendChannelList, recommendChannelList = recommendChannelList,
freeContentList = freeContentList, freeContentList = freeContentList,
pointAvailableContentList = pointAvailableContentList,
curationList = curationList curationList = curationList
) )
} }

View File

@@ -183,7 +183,9 @@ interface AudioContentQueryRepository {
offset: Long, offset: Long,
limit: Long, limit: Long,
isFree: Boolean, isFree: Boolean,
isAdult: Boolean isAdult: Boolean,
orderByRandom: Boolean = false,
isPointAvailableOnly: Boolean = false
): List<AudioContentMainItem> ): List<AudioContentMainItem>
fun findContentByCurationId( fun findContentByCurationId(
@@ -1308,7 +1310,9 @@ class AudioContentQueryRepositoryImpl(
offset: Long, offset: Long,
limit: Long, limit: Long,
isFree: Boolean, isFree: Boolean,
isAdult: Boolean isAdult: Boolean,
orderByRandom: Boolean,
isPointAvailableOnly: Boolean
): List<AudioContentMainItem> { ): List<AudioContentMainItem> {
var where = audioContent.isActive.isTrue var where = audioContent.isActive.isTrue
.and(audioContent.duration.isNotNull) .and(audioContent.duration.isNotNull)
@@ -1343,6 +1347,16 @@ class AudioContentQueryRepositoryImpl(
where = where.and(audioContent.price.loe(0)) where = where.and(audioContent.price.loe(0))
} }
if (isPointAvailableOnly) {
where = where.and(audioContent.isPointAvailable.isTrue)
}
val orderBy = if (orderByRandom) {
Expressions.numberTemplate(Double::class.java, "function('rand')").asc()
} else {
audioContent.releaseDate.desc()
}
return queryFactory return queryFactory
.select( .select(
QAudioContentMainItem( QAudioContentMainItem(
@@ -1360,7 +1374,7 @@ class AudioContentQueryRepositoryImpl(
.where(where) .where(where)
.offset(offset) .offset(offset)
.limit(limit) .limit(limit)
.orderBy(audioContent.releaseDate.desc()) .orderBy(orderBy)
.fetch() .fetch()
} }

View File

@@ -989,7 +989,9 @@ class AudioContentService(
offset: Long = 0, offset: Long = 0,
limit: Long = 20, limit: Long = 20,
isFree: Boolean = false, isFree: Boolean = false,
isAdult: Boolean = false isAdult: Boolean = false,
orderByRandom: Boolean = false,
isPointAvailableOnly: Boolean = false
): List<AudioContentMainItem> { ): List<AudioContentMainItem> {
return repository.getLatestContentByTheme( return repository.getLatestContentByTheme(
theme = theme, theme = theme,
@@ -997,7 +999,9 @@ class AudioContentService(
offset = offset, offset = offset,
limit = limit, limit = limit,
isFree = isFree, isFree = isFree,
isAdult = isAdult isAdult = isAdult,
orderByRandom = orderByRandom,
isPointAvailableOnly = isPointAvailableOnly
) )
} }
} }