Compare commits
12 Commits
9b4e2fd192
...
34590347a6
Author | SHA1 | Date |
---|---|---|
|
34590347a6 | |
|
14b25bdfc3 | |
|
7696f06fbd | |
|
38f6e8d870 | |
|
12c9b14168 | |
|
dc299f7727 | |
|
a983595bad | |
|
87a5ceee9c | |
|
3d514e8ad4 | |
|
94551b05ff | |
|
841e32a50b | |
|
cbcc63dc71 |
|
@ -0,0 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.admin.can
|
||||
|
||||
data class AdminCanChargeRequest(
|
||||
val memberId: Long,
|
||||
val method: String,
|
||||
val can: Int
|
||||
)
|
|
@ -0,0 +1,24 @@
|
|||
package kr.co.vividnext.sodalive.admin.can
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/can")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
class AdminCanController(private val service: AdminCanService) {
|
||||
@PostMapping
|
||||
fun insertCan(@RequestBody request: AdminCanRequest) = ApiResponse.ok(service.saveCan(request))
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
fun deleteCan(@PathVariable id: Long) = ApiResponse.ok(service.deleteCan(id))
|
||||
|
||||
@PostMapping("/charge")
|
||||
fun charge(@RequestBody request: AdminCanChargeRequest) = ApiResponse.ok(service.charge(request))
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package kr.co.vividnext.sodalive.admin.can
|
||||
|
||||
import kr.co.vividnext.sodalive.can.Can
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
||||
interface AdminCanRepository : JpaRepository<Can, Long>
|
|
@ -0,0 +1,26 @@
|
|||
package kr.co.vividnext.sodalive.admin.can
|
||||
|
||||
import kr.co.vividnext.sodalive.can.Can
|
||||
import kr.co.vividnext.sodalive.can.CanStatus
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
|
||||
data class AdminCanRequest(
|
||||
val can: Int,
|
||||
val rewardCan: Int,
|
||||
val price: Int
|
||||
) {
|
||||
fun toEntity(): Can {
|
||||
var title = "${can.moneyFormat()} 캔"
|
||||
if (rewardCan > 0) {
|
||||
title = "$title + ${rewardCan.moneyFormat()} 캔"
|
||||
}
|
||||
|
||||
return Can(
|
||||
title = title,
|
||||
can = can,
|
||||
rewardCan = rewardCan,
|
||||
price = price,
|
||||
status = CanStatus.SALE
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package kr.co.vividnext.sodalive.admin.can
|
||||
|
||||
import kr.co.vividnext.sodalive.admin.member.AdminMemberRepository
|
||||
import kr.co.vividnext.sodalive.can.CanStatus
|
||||
import kr.co.vividnext.sodalive.can.charge.Charge
|
||||
import kr.co.vividnext.sodalive.can.charge.ChargeRepository
|
||||
import kr.co.vividnext.sodalive.can.charge.ChargeStatus
|
||||
import kr.co.vividnext.sodalive.can.payment.Payment
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@Service
|
||||
class AdminCanService(
|
||||
private val repository: AdminCanRepository,
|
||||
private val chargeRepository: ChargeRepository,
|
||||
private val memberRepository: AdminMemberRepository
|
||||
) {
|
||||
@Transactional
|
||||
fun saveCan(request: AdminCanRequest) {
|
||||
repository.save(request.toEntity())
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun deleteCan(id: Long) {
|
||||
val can = repository.findByIdOrNull(id)
|
||||
?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
can.status = CanStatus.END_OF_SALE
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun charge(request: AdminCanChargeRequest) {
|
||||
val member = memberRepository.findByIdOrNull(request.memberId)
|
||||
?: throw SodaException("잘못된 회원번호 입니다.")
|
||||
|
||||
if (request.can <= 0) throw SodaException("0 코인 이상 입력하세요.")
|
||||
if (request.method.isBlank()) throw SodaException("기록내용을 입력하세요.")
|
||||
|
||||
val charge = Charge(0, request.can, status = ChargeStatus.ADMIN)
|
||||
charge.title = "${request.can.moneyFormat()} 캔"
|
||||
charge.member = member
|
||||
|
||||
val payment = Payment(status = PaymentStatus.COMPLETE, paymentGateway = PaymentGateway.PG)
|
||||
payment.method = request.method
|
||||
charge.payment = payment
|
||||
|
||||
chargeRepository.save(charge)
|
||||
|
||||
member.pgRewardCan += charge.rewardCan
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package kr.co.vividnext.sodalive.admin.charge
|
||||
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@RequestMapping("/admin/charge/status")
|
||||
class AdminChargeStatusController(private val service: AdminChargeStatusService) {
|
||||
@GetMapping
|
||||
fun getChargeStatus(
|
||||
@RequestParam startDateStr: String,
|
||||
@RequestParam endDateStr: String
|
||||
) = ApiResponse.ok(service.getChargeStatus(startDateStr, endDateStr))
|
||||
|
||||
@GetMapping("/detail")
|
||||
fun getChargeStatusDetail(
|
||||
@RequestParam startDateStr: String,
|
||||
@RequestParam paymentGateway: PaymentGateway
|
||||
) = ApiResponse.ok(service.getChargeStatusDetail(startDateStr, paymentGateway))
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package kr.co.vividnext.sodalive.admin.charge
|
||||
|
||||
import com.querydsl.core.types.dsl.Expressions
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.can.QCan.can1
|
||||
import kr.co.vividnext.sodalive.can.charge.ChargeStatus
|
||||
import kr.co.vividnext.sodalive.can.charge.QCharge.charge
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
|
||||
import kr.co.vividnext.sodalive.can.payment.QPayment.payment
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Repository
|
||||
class AdminChargeStatusQueryRepository(private val queryFactory: JPAQueryFactory) {
|
||||
fun getChargeStatus(startDate: LocalDateTime, endDate: LocalDateTime): List<GetChargeStatusQueryDto> {
|
||||
val formattedDate = Expressions.stringTemplate(
|
||||
"DATE_FORMAT({0}, {1})",
|
||||
Expressions.dateTimeTemplate(
|
||||
LocalDateTime::class.java,
|
||||
"CONVERT_TZ({0},{1},{2})",
|
||||
charge.createdAt,
|
||||
"UTC",
|
||||
"Asia/Seoul"
|
||||
),
|
||||
"%Y-%m-%d"
|
||||
)
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
QGetChargeStatusQueryDto(
|
||||
formattedDate,
|
||||
payment.price.sum(),
|
||||
can1.price.sum(),
|
||||
payment.id.count(),
|
||||
payment.paymentGateway
|
||||
)
|
||||
)
|
||||
.from(payment)
|
||||
.innerJoin(payment.charge, charge)
|
||||
.leftJoin(charge.can, can1)
|
||||
.where(
|
||||
charge.createdAt.goe(startDate)
|
||||
.and(charge.createdAt.loe(endDate))
|
||||
.and(charge.status.eq(ChargeStatus.CHARGE))
|
||||
.and(payment.status.eq(PaymentStatus.COMPLETE))
|
||||
)
|
||||
.groupBy(formattedDate, payment.paymentGateway)
|
||||
.orderBy(formattedDate.desc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun getChargeStatusDetail(startDate: LocalDateTime, endDate: LocalDateTime): List<GetChargeStatusDetailQueryDto> {
|
||||
val formattedDate = Expressions.stringTemplate(
|
||||
"DATE_FORMAT({0}, {1})",
|
||||
Expressions.dateTimeTemplate(
|
||||
LocalDateTime::class.java,
|
||||
"CONVERT_TZ({0},{1},{2})",
|
||||
charge.createdAt,
|
||||
"UTC",
|
||||
"Asia/Seoul"
|
||||
),
|
||||
"%Y-%m-%d %H:%i:%s"
|
||||
)
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
QGetChargeStatusDetailQueryDto(
|
||||
member.id,
|
||||
member.nickname,
|
||||
payment.method.coalesce(""),
|
||||
payment.price,
|
||||
can1.price,
|
||||
formattedDate
|
||||
)
|
||||
)
|
||||
.from(charge)
|
||||
.innerJoin(charge.member, member)
|
||||
.innerJoin(charge.payment, payment)
|
||||
.leftJoin(charge.can, can1)
|
||||
.where(
|
||||
charge.createdAt.goe(startDate)
|
||||
.and(charge.createdAt.loe(endDate))
|
||||
.and(charge.status.eq(ChargeStatus.CHARGE))
|
||||
.and(payment.status.eq(PaymentStatus.COMPLETE))
|
||||
)
|
||||
.orderBy(formattedDate.desc())
|
||||
.fetch()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package kr.co.vividnext.sodalive.admin.charge
|
||||
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Service
|
||||
class AdminChargeStatusService(val repository: AdminChargeStatusQueryRepository) {
|
||||
fun getChargeStatus(startDateStr: String, endDateStr: String): List<GetChargeStatusResponse> {
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||
val startDate = LocalDate.parse(startDateStr, dateTimeFormatter).atTime(0, 0, 0)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
|
||||
val endDate = LocalDate.parse(endDateStr, dateTimeFormatter).atTime(23, 59, 59)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
|
||||
var totalChargeAmount = 0
|
||||
var totalChargeCount = 0L
|
||||
|
||||
val chargeStatusList = repository.getChargeStatus(startDate, endDate)
|
||||
.asSequence()
|
||||
.map {
|
||||
val chargeAmount = if (it.paymentGateWay == PaymentGateway.APPLE_IAP) {
|
||||
it.appleChargeAmount.toInt()
|
||||
} else {
|
||||
it.pgChargeAmount
|
||||
}
|
||||
|
||||
val chargeCount = it.chargeCount
|
||||
|
||||
totalChargeAmount += chargeAmount
|
||||
totalChargeCount += chargeCount
|
||||
|
||||
GetChargeStatusResponse(
|
||||
date = it.date,
|
||||
chargeAmount = chargeAmount,
|
||||
chargeCount = chargeCount,
|
||||
pg = it.paymentGateWay.name
|
||||
)
|
||||
}
|
||||
.toMutableList()
|
||||
|
||||
chargeStatusList.add(
|
||||
0,
|
||||
GetChargeStatusResponse(
|
||||
date = "합계",
|
||||
chargeAmount = totalChargeAmount,
|
||||
chargeCount = totalChargeCount,
|
||||
pg = ""
|
||||
)
|
||||
)
|
||||
|
||||
return chargeStatusList.toList()
|
||||
}
|
||||
|
||||
fun getChargeStatusDetail(
|
||||
startDateStr: String,
|
||||
paymentGateway: PaymentGateway
|
||||
): List<GetChargeStatusDetailResponse> {
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||
val startDate = LocalDate.parse(startDateStr, dateTimeFormatter).atTime(0, 0, 0)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
|
||||
val endDate = LocalDate.parse(startDateStr, dateTimeFormatter).atTime(23, 59, 59)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
|
||||
return repository.getChargeStatusDetail(startDate, endDate)
|
||||
.asSequence()
|
||||
.filter {
|
||||
if (paymentGateway == PaymentGateway.APPLE_IAP) {
|
||||
it.appleChargeAmount > 0
|
||||
} else {
|
||||
it.pgChargeAmount > 0
|
||||
}
|
||||
}
|
||||
.map {
|
||||
GetChargeStatusDetailResponse(
|
||||
accountId = it.accountId,
|
||||
nickname = it.nickname,
|
||||
method = it.method,
|
||||
amount = if (paymentGateway == PaymentGateway.APPLE_IAP) {
|
||||
it.appleChargeAmount.toInt()
|
||||
} else {
|
||||
it.pgChargeAmount
|
||||
},
|
||||
datetime = it.datetime
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package kr.co.vividnext.sodalive.admin.charge
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
|
||||
data class GetChargeStatusDetailQueryDto @QueryProjection constructor(
|
||||
val accountId: Long,
|
||||
val nickname: String,
|
||||
val method: String,
|
||||
val appleChargeAmount: Double,
|
||||
val pgChargeAmount: Int,
|
||||
val datetime: String
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.admin.charge
|
||||
|
||||
data class GetChargeStatusDetailResponse(
|
||||
val accountId: Long,
|
||||
val nickname: String,
|
||||
val method: String,
|
||||
val amount: Int,
|
||||
val datetime: String
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
package kr.co.vividnext.sodalive.admin.charge
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||
|
||||
data class GetChargeStatusQueryDto @QueryProjection constructor(
|
||||
val date: String,
|
||||
val appleChargeAmount: Double,
|
||||
val pgChargeAmount: Int,
|
||||
val chargeCount: Long,
|
||||
val paymentGateWay: PaymentGateway
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
package kr.co.vividnext.sodalive.admin.charge
|
||||
|
||||
data class GetChargeStatusResponse(
|
||||
val date: String,
|
||||
val chargeAmount: Int,
|
||||
val chargeCount: Long,
|
||||
val pg: String
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
package kr.co.vividnext.sodalive.admin.content
|
||||
|
||||
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.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@RequestMapping("/admin/audio-content")
|
||||
class AdminContentController(private val service: AdminContentService) {
|
||||
@GetMapping("/list")
|
||||
fun getAudioContentList(pageable: Pageable) = ApiResponse.ok(service.getAudioContentList(pageable))
|
||||
|
||||
@GetMapping("/search")
|
||||
fun searchAudioContent(
|
||||
@RequestParam(value = "search_word") searchWord: String,
|
||||
pageable: Pageable
|
||||
) = ApiResponse.ok(service.searchAudioContent(searchWord, pageable))
|
||||
|
||||
@PutMapping
|
||||
fun modifyAudioContent(
|
||||
@RequestBody request: UpdateAdminContentRequest
|
||||
) = ApiResponse.ok(service.updateAudioContent(request))
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package kr.co.vividnext.sodalive.admin.content
|
||||
|
||||
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.content.AudioContent
|
||||
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
||||
import kr.co.vividnext.sodalive.content.hashtag.QAudioContentHashTag.audioContentHashTag
|
||||
import kr.co.vividnext.sodalive.content.hashtag.QHashTag.hashTag
|
||||
import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration
|
||||
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Repository
|
||||
interface AdminContentRepository : JpaRepository<AudioContent, Long>, AdminAudioContentQueryRepository
|
||||
|
||||
interface AdminAudioContentQueryRepository {
|
||||
fun getAudioContentTotalCount(searchWord: String = ""): Int
|
||||
fun getAudioContentList(offset: Long, limit: Long, searchWord: String = ""): List<GetAdminContentListItem>
|
||||
fun getHashTagList(audioContentId: Long): List<String>
|
||||
}
|
||||
|
||||
class AdminAudioContentQueryRepositoryImpl(
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : AdminAudioContentQueryRepository {
|
||||
override fun getAudioContentTotalCount(searchWord: String): Int {
|
||||
var where = audioContent.duration.isNotNull
|
||||
.and(audioContent.member.isNotNull)
|
||||
.and(audioContent.isActive.isTrue)
|
||||
|
||||
if (searchWord.trim().length > 1) {
|
||||
where = where.and(
|
||||
audioContent.title.contains(searchWord)
|
||||
.or(audioContent.member.nickname.contains(searchWord))
|
||||
)
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(audioContent.id)
|
||||
.from(audioContent)
|
||||
.where(where)
|
||||
.fetch()
|
||||
.size
|
||||
}
|
||||
|
||||
override fun getAudioContentList(offset: Long, limit: Long, searchWord: String): List<GetAdminContentListItem> {
|
||||
var where = audioContent.duration.isNotNull
|
||||
.and(audioContent.member.isNotNull)
|
||||
.and(audioContent.isActive.isTrue)
|
||||
|
||||
if (searchWord.trim().length > 1) {
|
||||
where = where.and(
|
||||
audioContent.title.contains(searchWord)
|
||||
.or(audioContent.member.nickname.contains(searchWord))
|
||||
)
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
QGetAdminContentListItem(
|
||||
audioContent.id,
|
||||
audioContent.title,
|
||||
audioContent.detail,
|
||||
audioContentCuration.title,
|
||||
audioContentCuration.id.nullif(0),
|
||||
audioContent.coverImage,
|
||||
audioContent.member!!.nickname,
|
||||
audioContentTheme.theme,
|
||||
audioContent.price,
|
||||
audioContent.isAdult,
|
||||
audioContent.duration,
|
||||
audioContent.content,
|
||||
formattedDateExpression(audioContent.createdAt)
|
||||
)
|
||||
)
|
||||
.from(audioContent)
|
||||
.leftJoin(audioContent.curation, audioContentCuration)
|
||||
.innerJoin(audioContent.theme, audioContentTheme)
|
||||
.where(where)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.orderBy(audioContent.id.desc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun getHashTagList(audioContentId: Long): List<String> {
|
||||
return queryFactory
|
||||
.select(hashTag.tag)
|
||||
.from(audioContentHashTag)
|
||||
.innerJoin(audioContentHashTag.hashTag, hashTag)
|
||||
.innerJoin(audioContentHashTag.audioContent, audioContent)
|
||||
.where(
|
||||
audioContent.duration.isNotNull
|
||||
.and(audioContent.member.isNotNull)
|
||||
.and(audioContentHashTag.audioContent.id.eq(audioContentId))
|
||||
)
|
||||
.fetch()
|
||||
}
|
||||
|
||||
private fun formattedDateExpression(
|
||||
dateTime: DateTimePath<LocalDateTime>,
|
||||
format: String = "%Y-%m-%d"
|
||||
): StringTemplate {
|
||||
return Expressions.stringTemplate(
|
||||
"DATE_FORMAT({0}, {1})",
|
||||
Expressions.dateTimeTemplate(
|
||||
LocalDateTime::class.java,
|
||||
"CONVERT_TZ({0},{1},{2})",
|
||||
dateTime,
|
||||
"UTC",
|
||||
"Asia/Seoul"
|
||||
),
|
||||
format
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package kr.co.vividnext.sodalive.admin.content
|
||||
|
||||
import kr.co.vividnext.sodalive.admin.content.curation.AdminContentCurationRepository
|
||||
import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@Service
|
||||
class AdminContentService(
|
||||
private val repository: AdminContentRepository,
|
||||
private val audioContentCloudFront: AudioContentCloudFront,
|
||||
private val curationRepository: AdminContentCurationRepository,
|
||||
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val coverImageHost: String
|
||||
) {
|
||||
fun getAudioContentList(pageable: Pageable): GetAdminContentListResponse {
|
||||
val totalCount = repository.getAudioContentTotalCount()
|
||||
val audioContentAndThemeList = repository.getAudioContentList(
|
||||
offset = pageable.offset,
|
||||
limit = pageable.pageSize.toLong()
|
||||
)
|
||||
|
||||
val audioContentList = audioContentAndThemeList
|
||||
.asSequence()
|
||||
.map {
|
||||
val tags = repository
|
||||
.getHashTagList(audioContentId = it.audioContentId)
|
||||
.joinToString(" ") { tag -> tag }
|
||||
it.tags = tags
|
||||
it
|
||||
}
|
||||
.map {
|
||||
it.contentUrl = audioContentCloudFront.generateSignedURL(
|
||||
resourcePath = it.contentUrl,
|
||||
expirationTime = 1000 * 60 * 60 * (it.remainingTime.split(":")[0].toLong() + 2)
|
||||
)
|
||||
it
|
||||
}
|
||||
.map {
|
||||
it.coverImageUrl = "$coverImageHost/${it.coverImageUrl}"
|
||||
it
|
||||
}
|
||||
.toList()
|
||||
|
||||
return GetAdminContentListResponse(totalCount, audioContentList)
|
||||
}
|
||||
|
||||
fun searchAudioContent(searchWord: String, pageable: Pageable): GetAdminContentListResponse {
|
||||
if (searchWord.length < 2) throw SodaException("2글자 이상 입력하세요.")
|
||||
val totalCount = repository.getAudioContentTotalCount(searchWord)
|
||||
val audioContentAndThemeList = repository.getAudioContentList(
|
||||
offset = pageable.offset,
|
||||
limit = pageable.pageSize.toLong(),
|
||||
searchWord = searchWord
|
||||
)
|
||||
|
||||
val audioContentList = audioContentAndThemeList
|
||||
.asSequence()
|
||||
.map {
|
||||
val tags = repository
|
||||
.getHashTagList(audioContentId = it.audioContentId)
|
||||
.joinToString(" ") { tag -> tag }
|
||||
it.tags = tags
|
||||
it
|
||||
}
|
||||
.map {
|
||||
it.contentUrl = audioContentCloudFront.generateSignedURL(
|
||||
resourcePath = it.contentUrl,
|
||||
expirationTime = 1000 * 60 * 60 * (it.remainingTime.split(":")[0].toLong() + 2)
|
||||
)
|
||||
it
|
||||
}
|
||||
.map {
|
||||
it.coverImageUrl = "$coverImageHost/${it.coverImageUrl}"
|
||||
it
|
||||
}
|
||||
.toList()
|
||||
|
||||
return GetAdminContentListResponse(totalCount, audioContentList)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateAudioContent(request: UpdateAdminContentRequest) {
|
||||
val audioContent = repository.findByIdOrNull(id = request.id)
|
||||
?: throw SodaException("없는 콘텐츠 입니다.")
|
||||
|
||||
if (request.isDefaultCoverImage) {
|
||||
audioContent.coverImage = "profile/default_profile.png"
|
||||
}
|
||||
|
||||
if (request.isActive != null) {
|
||||
audioContent.isActive = request.isActive
|
||||
}
|
||||
|
||||
if (request.isAdult != null) {
|
||||
audioContent.isAdult = request.isAdult
|
||||
}
|
||||
|
||||
if (request.isCommentAvailable != null) {
|
||||
audioContent.isCommentAvailable = request.isCommentAvailable
|
||||
}
|
||||
|
||||
if (request.title != null) {
|
||||
audioContent.title = request.title
|
||||
}
|
||||
|
||||
if (request.detail != null) {
|
||||
audioContent.detail = request.detail
|
||||
}
|
||||
|
||||
if (request.curationId != null) {
|
||||
val curation = curationRepository.findByIdAndActive(id = request.curationId)
|
||||
audioContent.curation = curation
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package kr.co.vividnext.sodalive.admin.content
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
|
||||
data class GetAdminContentListResponse(
|
||||
val totalCount: Int,
|
||||
val items: List<GetAdminContentListItem>
|
||||
)
|
||||
|
||||
data class GetAdminContentListItem @QueryProjection constructor(
|
||||
val audioContentId: Long,
|
||||
val title: String,
|
||||
val detail: String,
|
||||
val curationTitle: String?,
|
||||
val curationId: Long,
|
||||
var coverImageUrl: String,
|
||||
val creatorNickname: String,
|
||||
val theme: String,
|
||||
val price: Int,
|
||||
val isAdult: Boolean,
|
||||
val remainingTime: String,
|
||||
var contentUrl: String,
|
||||
val date: String
|
||||
) {
|
||||
var tags: String = ""
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package kr.co.vividnext.sodalive.admin.content
|
||||
|
||||
data class UpdateAdminContentRequest(
|
||||
val id: Long,
|
||||
val isDefaultCoverImage: Boolean,
|
||||
val title: String?,
|
||||
val detail: String?,
|
||||
val curationId: Long?,
|
||||
val isAdult: Boolean?,
|
||||
val isActive: Boolean?,
|
||||
val isCommentAvailable: Boolean?
|
||||
)
|
|
@ -0,0 +1,37 @@
|
|||
package kr.co.vividnext.sodalive.admin.content.banner
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
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.RequestPart
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/audio-content/banner")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
class AdminContentBannerController(private val service: AdminContentBannerService) {
|
||||
@PostMapping
|
||||
fun createAudioContentMainBanner(
|
||||
@RequestPart("image") image: MultipartFile,
|
||||
@RequestPart("request") requestString: String
|
||||
) = ApiResponse.ok(service.createAudioContentMainBanner(image, requestString))
|
||||
|
||||
@PutMapping
|
||||
fun modifyAudioContentMainBanner(
|
||||
@RequestPart("image", required = false) image: MultipartFile? = null,
|
||||
@RequestPart("request") requestString: String
|
||||
) = ApiResponse.ok(service.updateAudioContentMainBanner(image, requestString))
|
||||
|
||||
@PutMapping("/orders")
|
||||
fun updateBannerOrders(
|
||||
@RequestBody request: UpdateBannerOrdersRequest
|
||||
) = ApiResponse.ok(service.updateBannerOrders(request.ids), "수정되었습니다.")
|
||||
|
||||
@GetMapping
|
||||
fun getAudioContentMainBannerList() = ApiResponse.ok(service.getAudioContentMainBannerList())
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package kr.co.vividnext.sodalive.admin.content.banner
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner
|
||||
import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner
|
||||
import kr.co.vividnext.sodalive.event.QEvent.event
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface AdminContentBannerRepository : JpaRepository<AudioContentBanner, Long>, AdminContentBannerQueryRepository
|
||||
|
||||
interface AdminContentBannerQueryRepository {
|
||||
fun getAudioContentMainBannerList(): List<GetAdminContentBannerResponse>
|
||||
}
|
||||
|
||||
class AdminContentBannerQueryRepositoryImpl(
|
||||
private val queryFactory: JPAQueryFactory,
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) : AdminContentBannerQueryRepository {
|
||||
override fun getAudioContentMainBannerList(): List<GetAdminContentBannerResponse> {
|
||||
return queryFactory
|
||||
.select(
|
||||
QGetAdminContentBannerResponse(
|
||||
audioContentBanner.id,
|
||||
audioContentBanner.type,
|
||||
audioContentBanner.thumbnailImage.prepend("/").prepend(cloudFrontHost),
|
||||
audioContentBanner.event.id,
|
||||
audioContentBanner.event.thumbnailImage,
|
||||
audioContentBanner.creator.id,
|
||||
audioContentBanner.creator.nickname,
|
||||
audioContentBanner.link,
|
||||
audioContentBanner.isAdult
|
||||
)
|
||||
)
|
||||
.from(audioContentBanner)
|
||||
.leftJoin(audioContentBanner.event, event)
|
||||
.leftJoin(audioContentBanner.creator, member)
|
||||
.where(audioContentBanner.isActive.isTrue)
|
||||
.orderBy(audioContentBanner.orders.asc())
|
||||
.fetch()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package kr.co.vividnext.sodalive.admin.content.banner
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner
|
||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||
import kr.co.vividnext.sodalive.event.EventRepository
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@Service
|
||||
class AdminContentBannerService(
|
||||
private val s3Uploader: S3Uploader,
|
||||
private val repository: AdminContentBannerRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val eventRepository: EventRepository,
|
||||
private val objectMapper: ObjectMapper,
|
||||
|
||||
@Value("\${cloud.aws.s3.bucket}")
|
||||
private val bucket: String
|
||||
) {
|
||||
@Transactional
|
||||
fun createAudioContentMainBanner(image: MultipartFile, requestString: String) {
|
||||
val request = objectMapper.readValue(requestString, CreateContentBannerRequest::class.java)
|
||||
if (request.type == AudioContentBannerType.CREATOR && request.creatorId == null) {
|
||||
throw SodaException("크리에이터를 선택하세요.")
|
||||
}
|
||||
|
||||
if (request.type == AudioContentBannerType.LINK && request.link == null) {
|
||||
throw SodaException("링크 url을 입력하세요.")
|
||||
}
|
||||
|
||||
if (request.type == AudioContentBannerType.EVENT && request.eventId == null) {
|
||||
throw SodaException("이벤트를 선택하세요.")
|
||||
}
|
||||
|
||||
val event = if (request.eventId != null && request.eventId > 0) {
|
||||
eventRepository.findByIdOrNull(request.eventId)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val creator = if (request.creatorId != null && request.creatorId > 0) {
|
||||
memberRepository.findByIdOrNull(request.creatorId)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val audioContentBanner = AudioContentBanner(type = request.type)
|
||||
audioContentBanner.link = request.link
|
||||
audioContentBanner.isAdult = request.isAdult
|
||||
audioContentBanner.event = event
|
||||
audioContentBanner.creator = creator
|
||||
repository.save(audioContentBanner)
|
||||
|
||||
val fileName = generateFileName()
|
||||
val imagePath = s3Uploader.upload(
|
||||
inputStream = image.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "audio_content_banner/${audioContentBanner.id}/$fileName"
|
||||
)
|
||||
audioContentBanner.thumbnailImage = imagePath
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateAudioContentMainBanner(image: MultipartFile?, requestString: String) {
|
||||
val request = objectMapper.readValue(requestString, UpdateContentBannerRequest::class.java)
|
||||
val audioContentBanner = repository.findByIdOrNull(request.id)
|
||||
?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
if (image != null) {
|
||||
val fileName = generateFileName()
|
||||
val imagePath = s3Uploader.upload(
|
||||
inputStream = image.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "audio_content_banner/${audioContentBanner.id}/$fileName"
|
||||
)
|
||||
audioContentBanner.thumbnailImage = imagePath
|
||||
}
|
||||
|
||||
if (request.isAdult != null) {
|
||||
audioContentBanner.isAdult = request.isAdult
|
||||
}
|
||||
|
||||
if (request.isActive != null) {
|
||||
audioContentBanner.isActive = request.isActive
|
||||
}
|
||||
|
||||
if (request.type != null) {
|
||||
audioContentBanner.creator = null
|
||||
audioContentBanner.event = null
|
||||
audioContentBanner.link = null
|
||||
|
||||
if (request.type == AudioContentBannerType.CREATOR) {
|
||||
if (request.creatorId != null) {
|
||||
val creator = memberRepository.findByIdOrNull(request.creatorId)
|
||||
?: throw SodaException("크리에이터를 선택하세요.")
|
||||
|
||||
audioContentBanner.creator = creator
|
||||
} else {
|
||||
throw SodaException("크리에이터를 선택하세요.")
|
||||
}
|
||||
} else if (request.type == AudioContentBannerType.LINK) {
|
||||
if (request.link != null) {
|
||||
audioContentBanner.link = request.link
|
||||
} else {
|
||||
throw SodaException("링크 url을 입력하세요.")
|
||||
}
|
||||
} else if (request.type == AudioContentBannerType.EVENT) {
|
||||
if (request.eventId != null) {
|
||||
val event = eventRepository.findByIdOrNull(request.eventId)
|
||||
?: throw SodaException("이벤트를 선택하세요.")
|
||||
|
||||
audioContentBanner.event = event
|
||||
} else {
|
||||
throw SodaException("이벤트를 선택하세요.")
|
||||
}
|
||||
}
|
||||
|
||||
audioContentBanner.type = request.type
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateBannerOrders(ids: List<Long>) {
|
||||
for (index in ids.indices) {
|
||||
val tag = repository.findByIdOrNull(ids[index])
|
||||
|
||||
if (tag != null) {
|
||||
tag.orders = index + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getAudioContentMainBannerList(): List<GetAdminContentBannerResponse> {
|
||||
return repository.getAudioContentMainBannerList()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.admin.content.banner
|
||||
|
||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||
|
||||
data class CreateContentBannerRequest(
|
||||
val type: AudioContentBannerType,
|
||||
val eventId: Long?,
|
||||
val creatorId: Long?,
|
||||
val link: String?,
|
||||
val isAdult: Boolean
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
package kr.co.vividnext.sodalive.admin.content.banner
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||
|
||||
data class GetAdminContentBannerResponse @QueryProjection constructor(
|
||||
val id: Long,
|
||||
val type: AudioContentBannerType,
|
||||
val thumbnailImageUrl: String,
|
||||
val eventId: Long?,
|
||||
val eventThumbnailImage: String?,
|
||||
val creatorId: Long?,
|
||||
val creatorNickname: String?,
|
||||
val link: String?,
|
||||
val isAdult: Boolean
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
package kr.co.vividnext.sodalive.admin.content.banner
|
||||
|
||||
data class UpdateBannerOrdersRequest(
|
||||
val ids: List<Long>
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package kr.co.vividnext.sodalive.admin.content.banner
|
||||
|
||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||
|
||||
data class UpdateContentBannerRequest(
|
||||
val id: Long,
|
||||
val type: AudioContentBannerType?,
|
||||
val eventId: Long?,
|
||||
val creatorId: Long?,
|
||||
val link: String?,
|
||||
val isAdult: Boolean?,
|
||||
val isActive: Boolean?
|
||||
)
|
|
@ -0,0 +1,33 @@
|
|||
package kr.co.vividnext.sodalive.admin.content.curation
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
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/audio-content/curation")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
class AdminContentCurationController(private val service: AdminContentCurationService) {
|
||||
@PostMapping
|
||||
fun createContentCuration(
|
||||
@RequestBody request: CreateContentCurationRequest
|
||||
) = ApiResponse.ok(service.createContentCuration(request))
|
||||
|
||||
@PutMapping
|
||||
fun updateContentCuration(
|
||||
@RequestBody request: UpdateContentCurationRequest
|
||||
) = ApiResponse.ok(service.updateContentCuration(request))
|
||||
|
||||
@PutMapping("/orders")
|
||||
fun updateContentCurationOrders(
|
||||
@RequestBody request: UpdateContentCurationOrdersRequest
|
||||
) = ApiResponse.ok(service.updateContentCurationOrders(request.ids), "수정되었습니다.")
|
||||
|
||||
@GetMapping
|
||||
fun getContentCurationList() = ApiResponse.ok(service.getContentCurationList())
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package kr.co.vividnext.sodalive.admin.content.curation
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration
|
||||
import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface AdminContentCurationRepository :
|
||||
JpaRepository<AudioContentCuration, Long>,
|
||||
AdminContentCurationQueryRepository
|
||||
|
||||
interface AdminContentCurationQueryRepository {
|
||||
fun getAudioContentCurationList(): List<GetAdminContentCurationResponse>
|
||||
fun findByIdAndActive(id: Long): AudioContentCuration?
|
||||
}
|
||||
|
||||
@Repository
|
||||
class AdminContentCurationQueryRepositoryImpl(
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : AdminContentCurationQueryRepository {
|
||||
override fun getAudioContentCurationList(): List<GetAdminContentCurationResponse> {
|
||||
return queryFactory
|
||||
.select(
|
||||
QGetAdminContentCurationResponse(
|
||||
audioContentCuration.id,
|
||||
audioContentCuration.title,
|
||||
audioContentCuration.description,
|
||||
audioContentCuration.isAdult
|
||||
)
|
||||
)
|
||||
.from(audioContentCuration)
|
||||
.where(audioContentCuration.isActive.isTrue)
|
||||
.orderBy(audioContentCuration.orders.asc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun findByIdAndActive(id: Long): AudioContentCuration? {
|
||||
return queryFactory
|
||||
.selectFrom(audioContentCuration)
|
||||
.where(
|
||||
audioContentCuration.id.eq(id)
|
||||
.and(audioContentCuration.isActive.isTrue)
|
||||
)
|
||||
.fetchFirst()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package kr.co.vividnext.sodalive.admin.content.curation
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@Service
|
||||
class AdminContentCurationService(
|
||||
private val repository: AdminContentCurationRepository
|
||||
) {
|
||||
@Transactional
|
||||
fun createContentCuration(request: CreateContentCurationRequest) {
|
||||
repository.save(
|
||||
AudioContentCuration(
|
||||
title = request.title,
|
||||
description = request.description,
|
||||
isAdult = request.isAdult
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateContentCuration(request: UpdateContentCurationRequest) {
|
||||
val audioContentCuration = repository.findByIdOrNull(id = request.id)
|
||||
?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
if (request.title != null) {
|
||||
audioContentCuration.title = request.title
|
||||
}
|
||||
|
||||
if (request.description != null) {
|
||||
audioContentCuration.description = request.description
|
||||
}
|
||||
|
||||
if (request.isAdult != null) {
|
||||
audioContentCuration.isAdult = request.isAdult
|
||||
}
|
||||
|
||||
if (request.isActive != null) {
|
||||
audioContentCuration.isActive = request.isActive
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateContentCurationOrders(ids: List<Long>) {
|
||||
for (index in ids.indices) {
|
||||
val audioContentCuration = repository.findByIdOrNull(ids[index])
|
||||
|
||||
if (audioContentCuration != null) {
|
||||
audioContentCuration.orders = index + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getContentCurationList(): List<GetAdminContentCurationResponse> {
|
||||
return repository.getAudioContentCurationList()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package kr.co.vividnext.sodalive.admin.content.curation
|
||||
|
||||
data class CreateContentCurationRequest(
|
||||
val title: String,
|
||||
val description: String,
|
||||
val isAdult: Boolean
|
||||
)
|
||||
|
||||
data class UpdateContentCurationRequest(
|
||||
val id: Long,
|
||||
val title: String?,
|
||||
val description: String?,
|
||||
val isAdult: Boolean?,
|
||||
val isActive: Boolean?
|
||||
)
|
||||
|
||||
data class UpdateContentCurationOrdersRequest(
|
||||
val ids: List<Long>
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package kr.co.vividnext.sodalive.admin.content.curation
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
|
||||
data class GetAdminContentCurationResponse @QueryProjection constructor(
|
||||
val id: Long,
|
||||
val title: String,
|
||||
val description: String,
|
||||
val isAdult: Boolean
|
||||
)
|
|
@ -0,0 +1,32 @@
|
|||
package kr.co.vividnext.sodalive.admin.event
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
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("/event/charge")
|
||||
class AdminChargeEventController(private val service: AdminChargeEventService) {
|
||||
@PostMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun createChargeEvent(@RequestBody request: CreateChargeEventRequest): ApiResponse<Any> {
|
||||
service.createChargeEvent(request)
|
||||
return ApiResponse.ok(null, "등록되었습니다.")
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun modifyChargeEvent(@RequestBody request: ModifyChargeEventRequest) = ApiResponse.ok(
|
||||
service.modifyChargeEvent(request),
|
||||
"수정되었습니다."
|
||||
)
|
||||
|
||||
@GetMapping("/list")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun getChargeEventList() = ApiResponse.ok(service.getChargeEventList())
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package kr.co.vividnext.sodalive.admin.event
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.admin.event.QChargeEvent.chargeEvent
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface AdminChargeEventRepository : JpaRepository<ChargeEvent, Long>, AdminChargeEventQueryRepository
|
||||
|
||||
interface AdminChargeEventQueryRepository {
|
||||
fun getChargeEventList(): List<ChargeEvent>
|
||||
}
|
||||
|
||||
class AdminChargeEventQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : AdminChargeEventQueryRepository {
|
||||
override fun getChargeEventList(): List<ChargeEvent> {
|
||||
return queryFactory
|
||||
.selectFrom(chargeEvent)
|
||||
.orderBy(chargeEvent.createdAt.desc())
|
||||
.fetch()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package kr.co.vividnext.sodalive.admin.event
|
||||
|
||||
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.LocalDate
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class AdminChargeEventService(private val repository: AdminChargeEventRepository) {
|
||||
@Transactional
|
||||
fun createChargeEvent(request: CreateChargeEventRequest): Long {
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||
val startDate = LocalDate.parse(request.startDateString, dateTimeFormatter).atTime(0, 0)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
|
||||
val endDate = LocalDate.parse(request.endDateString, dateTimeFormatter).atTime(23, 59, 59)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
|
||||
val chargeEvent = ChargeEvent(
|
||||
title = request.title,
|
||||
startDate = startDate,
|
||||
endDate = endDate,
|
||||
availableCount = request.availableCount,
|
||||
addPercent = request.addPercent / 100f
|
||||
)
|
||||
|
||||
return repository.save(chargeEvent).id!!
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun modifyChargeEvent(request: ModifyChargeEventRequest) {
|
||||
val chargeEvent = repository.findByIdOrNull(request.id)
|
||||
?: throw SodaException("해당하는 충전이벤트가 없습니다\n다시 시도해 주세요.")
|
||||
|
||||
if (request.title != null) {
|
||||
chargeEvent.title = request.title
|
||||
}
|
||||
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||
if (request.startDateString != null) {
|
||||
chargeEvent.startDate = LocalDate.parse(request.startDateString, dateTimeFormatter).atTime(0, 0)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
}
|
||||
|
||||
if (request.endDateString != null) {
|
||||
chargeEvent.endDate = LocalDate.parse(request.endDateString, dateTimeFormatter).atTime(23, 59, 59)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
}
|
||||
|
||||
if (request.availableCount != null) {
|
||||
chargeEvent.availableCount = request.availableCount
|
||||
}
|
||||
|
||||
if (request.addPercent != null) {
|
||||
chargeEvent.addPercent = request.addPercent / 100f
|
||||
}
|
||||
|
||||
if (request.isActive != null) {
|
||||
chargeEvent.isActive = request.isActive
|
||||
}
|
||||
}
|
||||
|
||||
fun getChargeEventList(): List<GetChargeEventListResponse> {
|
||||
return repository.getChargeEventList()
|
||||
.map {
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||
val startDate = it.startDate
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of("Asia/Seoul"))
|
||||
.format(dateTimeFormatter)
|
||||
|
||||
val endDate = it.endDate
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of("Asia/Seoul"))
|
||||
.format(dateTimeFormatter)
|
||||
|
||||
GetChargeEventListResponse(
|
||||
id = it.id!!,
|
||||
title = it.title,
|
||||
startDate = startDate,
|
||||
endDate = endDate,
|
||||
availableCount = it.availableCount,
|
||||
addPercent = (it.addPercent * 100).toInt(),
|
||||
isActive = it.isActive
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package kr.co.vividnext.sodalive.admin.event
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.Entity
|
||||
|
||||
@Entity
|
||||
data class ChargeEvent(
|
||||
var title: String,
|
||||
var startDate: LocalDateTime,
|
||||
var endDate: LocalDateTime,
|
||||
var availableCount: Int,
|
||||
var addPercent: Float,
|
||||
var isActive: Boolean = true
|
||||
) : BaseEntity()
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.admin.event
|
||||
|
||||
data class CreateChargeEventRequest(
|
||||
val title: String,
|
||||
val startDateString: String,
|
||||
val endDateString: String,
|
||||
val availableCount: Int,
|
||||
val addPercent: Int
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.admin.event
|
||||
|
||||
data class GetChargeEventListResponse(
|
||||
val id: Long,
|
||||
val title: String,
|
||||
val startDate: String,
|
||||
val endDate: String,
|
||||
val availableCount: Int,
|
||||
val addPercent: Int,
|
||||
val isActive: Boolean
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.admin.event
|
||||
|
||||
data class ModifyChargeEventRequest(
|
||||
val id: Long,
|
||||
val title: String? = null,
|
||||
val startDateString: String? = null,
|
||||
val endDateString: String? = null,
|
||||
val availableCount: Int? = null,
|
||||
val addPercent: Int? = null,
|
||||
val isActive: Boolean? = null
|
||||
)
|
|
@ -0,0 +1,58 @@
|
|||
package kr.co.vividnext.sodalive.admin.live
|
||||
|
||||
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.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.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/live")
|
||||
class AdminLiveController(private val service: AdminLiveService) {
|
||||
@GetMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun getOnAirLive() = ApiResponse.ok(data = service.getLiveList())
|
||||
|
||||
@GetMapping("/recommend-creator")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun getRecommendCreatorBanner(pageable: Pageable) = ApiResponse.ok(service.getRecommendCreator(pageable))
|
||||
|
||||
@PostMapping("/recommend-creator")
|
||||
fun createRecommendCreatorBanner(
|
||||
@RequestParam("image") image: MultipartFile,
|
||||
@RequestParam("creator_id") creatorId: Long,
|
||||
@RequestParam("start_date") startDate: String,
|
||||
@RequestParam("end_date") endDate: String,
|
||||
@RequestParam("is_adult") isAdult: Boolean
|
||||
) = ApiResponse.ok(
|
||||
service.createRecommendCreatorBanner(image, creatorId, startDate, endDate, isAdult),
|
||||
"등록되었습니다."
|
||||
)
|
||||
|
||||
@PutMapping("/recommend-creator")
|
||||
fun updateRecommendCreatorBanner(
|
||||
@RequestParam("recommend_creator_banner_id") recommendCreatorBannerId: Long,
|
||||
@RequestParam("image", required = false) image: MultipartFile?,
|
||||
@RequestParam("creator_id", required = false) creatorId: Long?,
|
||||
@RequestParam("start_date", required = false) startDate: String?,
|
||||
@RequestParam("end_date", required = false) endDate: String?,
|
||||
@RequestParam("is_adult", required = false) isAdult: Boolean?
|
||||
) = ApiResponse.ok(
|
||||
service.updateRecommendCreatorBanner(recommendCreatorBannerId, image, creatorId, startDate, endDate, isAdult),
|
||||
"수정되었습니다."
|
||||
)
|
||||
|
||||
@PutMapping("/recommend-creator/orders")
|
||||
fun updateRecommendCreatorBannerOrders(
|
||||
@RequestBody request: UpdateAdminRecommendCreatorBannerOrdersRequest
|
||||
) = ApiResponse.ok(
|
||||
service.updateRecommendCreatorBannerOrders(request.firstOrders, request.ids),
|
||||
"수정되었습니다."
|
||||
)
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package kr.co.vividnext.sodalive.admin.live
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.live.recommend.QRecommendLiveCreatorBanner.recommendLiveCreatorBanner
|
||||
import kr.co.vividnext.sodalive.live.recommend.RecommendLiveCreatorBanner
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
class AdminLiveRoomQueryRepository(private val queryFactory: JPAQueryFactory) {
|
||||
fun getLiveRoomList(): List<LiveRoom> {
|
||||
return queryFactory
|
||||
.selectFrom(liveRoom)
|
||||
.innerJoin(liveRoom.member, member)
|
||||
.where(liveRoom.isActive.isTrue)
|
||||
.orderBy(liveRoom.channelName.desc(), liveRoom.beginDateTime.asc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun getRecommendCreatorTotalCount(): Int {
|
||||
return queryFactory
|
||||
.select(recommendLiveCreatorBanner.id)
|
||||
.from(recommendLiveCreatorBanner)
|
||||
.fetch()
|
||||
.size
|
||||
}
|
||||
|
||||
fun getRecommendCreatorList(pageable: Pageable): List<RecommendLiveCreatorBanner> {
|
||||
return queryFactory
|
||||
.selectFrom(recommendLiveCreatorBanner)
|
||||
.offset(pageable.offset)
|
||||
.limit(pageable.pageSize.toLong())
|
||||
.orderBy(recommendLiveCreatorBanner.orders.asc(), recommendLiveCreatorBanner.id.desc())
|
||||
.fetch()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
package kr.co.vividnext.sodalive.admin.live
|
||||
|
||||
import com.amazonaws.services.s3.model.ObjectMetadata
|
||||
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.live.recommend.RecommendLiveCreatorBanner
|
||||
import kr.co.vividnext.sodalive.live.recommend.RecommendLiveCreatorBannerRepository
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Service
|
||||
class AdminLiveService(
|
||||
private val recommendCreatorBannerRepository: RecommendLiveCreatorBannerRepository,
|
||||
private val repository: AdminLiveRoomQueryRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val s3Uploader: S3Uploader,
|
||||
|
||||
@Value("\${cloud.aws.s3.bucket}")
|
||||
private val bucket: String,
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val coverImageHost: String
|
||||
) {
|
||||
fun getLiveList(): GetLiveResponse {
|
||||
return GetLiveResponse(
|
||||
liveList = repository.getLiveRoomList()
|
||||
.asSequence()
|
||||
.map {
|
||||
GetLiveResponseItem(
|
||||
id = it.id!!,
|
||||
title = it.title,
|
||||
content = it.notice,
|
||||
managerNickname = it.member!!.nickname,
|
||||
coverImageUrl = if (it.coverImage!!.startsWith("https://")) {
|
||||
it.coverImage!!
|
||||
} else {
|
||||
"$coverImageHost/${it.coverImage!!}"
|
||||
},
|
||||
channelName = it.channelName ?: "",
|
||||
type = it.type,
|
||||
password = it.password,
|
||||
isAdult = it.isAdult
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
)
|
||||
}
|
||||
|
||||
fun getRecommendCreator(pageable: Pageable): GetAdminRecommendCreatorResponse {
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
|
||||
|
||||
val totalCount = repository.getRecommendCreatorTotalCount()
|
||||
|
||||
val recommendCreatorList = repository
|
||||
.getRecommendCreatorList(pageable)
|
||||
.asSequence()
|
||||
.map {
|
||||
GetAdminRecommendCreatorResponseItem(
|
||||
it.id!!,
|
||||
"$coverImageHost/${it.image}",
|
||||
it.creator!!.id!!,
|
||||
it.creator!!.nickname,
|
||||
it.startDate
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of("Asia/Seoul"))
|
||||
.toLocalDateTime()
|
||||
.format(dateTimeFormatter),
|
||||
it.endDate
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of("Asia/Seoul"))
|
||||
.toLocalDateTime()
|
||||
.format(dateTimeFormatter),
|
||||
it.isAdult
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
|
||||
return GetAdminRecommendCreatorResponse(
|
||||
totalCount = totalCount,
|
||||
recommendCreatorList = recommendCreatorList
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun createRecommendCreatorBanner(
|
||||
image: MultipartFile,
|
||||
creatorId: Long,
|
||||
startDateString: String,
|
||||
endDateString: String,
|
||||
isAdult: Boolean
|
||||
): Long {
|
||||
if (creatorId < 1) throw SodaException("올바른 크리에이터를 선택해 주세요.")
|
||||
|
||||
val creator = memberRepository.findCreatorByIdOrNull(memberId = creatorId)
|
||||
?: throw SodaException("올바른 크리에이터를 선택해 주세요.")
|
||||
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
|
||||
val startDate = LocalDateTime.parse(startDateString, dateTimeFormatter)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
|
||||
val nowDate = LocalDateTime.now()
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
|
||||
if (startDate < nowDate) throw SodaException("노출 시작일은 현재시간 이후로 설정하셔야 합니다.")
|
||||
|
||||
val endDate = LocalDateTime.parse(endDateString, dateTimeFormatter)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
|
||||
if (endDate < nowDate) throw SodaException("노출 종료일은 현재시간 이후로 설정하셔야 합니다.")
|
||||
if (endDate <= startDate) throw SodaException("노출 시작일은 노출 종료일 이전으로 설정하셔야 합니다.")
|
||||
|
||||
val recommendCreatorBanner = RecommendLiveCreatorBanner(
|
||||
startDate = startDate,
|
||||
endDate = endDate,
|
||||
isAdult = isAdult
|
||||
)
|
||||
recommendCreatorBanner.creator = creator
|
||||
recommendCreatorBannerRepository.save(recommendCreatorBanner)
|
||||
|
||||
val metadata = ObjectMetadata()
|
||||
metadata.contentLength = image.size
|
||||
val imagePath = s3Uploader.upload(
|
||||
inputStream = image.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "recommend_creator_banner/${recommendCreatorBanner.id}/${generateFileName()}",
|
||||
metadata = metadata
|
||||
)
|
||||
|
||||
recommendCreatorBanner.image = imagePath
|
||||
|
||||
return recommendCreatorBanner.id!!
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateRecommendCreatorBanner(
|
||||
recommendCreatorBannerId: Long,
|
||||
image: MultipartFile?,
|
||||
creatorId: Long?,
|
||||
startDateString: String?,
|
||||
endDateString: String?,
|
||||
isAdult: Boolean?
|
||||
) {
|
||||
val recommendCreatorBanner = recommendCreatorBannerRepository.findByIdOrNull(recommendCreatorBannerId)
|
||||
?: throw SodaException("해당하는 추천라이브가 없습니다. 다시 확인해 주세요.")
|
||||
|
||||
if (creatorId != null) {
|
||||
if (creatorId < 1) throw SodaException("올바른 크리에이터를 선택해 주세요.")
|
||||
|
||||
val creator = memberRepository.findCreatorByIdOrNull(memberId = creatorId)
|
||||
?: throw SodaException("올바른 크리에이터를 선택해 주세요.")
|
||||
|
||||
recommendCreatorBanner.creator = creator
|
||||
}
|
||||
|
||||
if (image != null) {
|
||||
val metadata = ObjectMetadata()
|
||||
metadata.contentLength = image.size
|
||||
val imagePath = s3Uploader.upload(
|
||||
inputStream = image.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "recommend_creator_banner/${recommendCreatorBanner.id}/${generateFileName()}",
|
||||
metadata = metadata
|
||||
)
|
||||
|
||||
recommendCreatorBanner.image = imagePath
|
||||
}
|
||||
|
||||
if (startDateString != null) {
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
|
||||
val startDate = LocalDateTime.parse(startDateString, dateTimeFormatter)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
|
||||
val endDate = if (endDateString != null) {
|
||||
LocalDateTime.parse(endDateString, dateTimeFormatter)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (endDate != null) {
|
||||
if (endDate <= startDate) {
|
||||
throw SodaException("노출 시작일은 노출 종료일 이전으로 설정하셔야 합니다.")
|
||||
}
|
||||
|
||||
recommendCreatorBanner.endDate = endDate
|
||||
} else {
|
||||
if (recommendCreatorBanner.endDate <= startDate) {
|
||||
throw SodaException("노출 시작일은 노출 종료일 이전으로 설정하셔야 합니다.")
|
||||
}
|
||||
}
|
||||
|
||||
recommendCreatorBanner.startDate = startDate
|
||||
} else if (endDateString != null) {
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
|
||||
val endDate = LocalDateTime.parse(endDateString, dateTimeFormatter)
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
|
||||
if (endDate <= recommendCreatorBanner.startDate) {
|
||||
throw SodaException("노출 종료일은 노출 시작일 이후로 설정하셔야 합니다.")
|
||||
}
|
||||
|
||||
recommendCreatorBanner.endDate = endDate
|
||||
}
|
||||
|
||||
if (isAdult != null) {
|
||||
recommendCreatorBanner.isAdult = isAdult
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateRecommendCreatorBannerOrders(firstOrders: Int, ids: List<Long>) {
|
||||
for (index in ids.indices) {
|
||||
val recommendCreatorBanner = recommendCreatorBannerRepository.findByIdOrNull(id = ids[index])
|
||||
|
||||
if (recommendCreatorBanner != null) {
|
||||
recommendCreatorBanner.orders = firstOrders + index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package kr.co.vividnext.sodalive.admin.live
|
||||
|
||||
data class GetAdminRecommendCreatorResponse(
|
||||
val totalCount: Int,
|
||||
val recommendCreatorList: List<GetAdminRecommendCreatorResponseItem>
|
||||
)
|
||||
|
||||
data class GetAdminRecommendCreatorResponseItem(
|
||||
val id: Long,
|
||||
val image: String,
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val startDate: String,
|
||||
val endDate: String,
|
||||
val isAdult: Boolean
|
||||
)
|
|
@ -0,0 +1,19 @@
|
|||
package kr.co.vividnext.sodalive.admin.live
|
||||
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
||||
|
||||
data class GetLiveResponse(
|
||||
val liveList: List<GetLiveResponseItem>
|
||||
)
|
||||
|
||||
data class GetLiveResponseItem(
|
||||
val id: Long,
|
||||
val title: String,
|
||||
val content: String,
|
||||
val managerNickname: String,
|
||||
val coverImageUrl: String,
|
||||
val channelName: String,
|
||||
val type: LiveRoomType,
|
||||
val password: String?,
|
||||
val isAdult: Boolean
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
package kr.co.vividnext.sodalive.admin.live
|
||||
|
||||
data class UpdateAdminRecommendCreatorBannerOrdersRequest(
|
||||
val firstOrders: Int,
|
||||
val ids: List<Long>
|
||||
)
|
|
@ -0,0 +1,35 @@
|
|||
package kr.co.vividnext.sodalive.admin.member
|
||||
|
||||
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.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/member")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
class AdminMemberController(private val service: AdminMemberService) {
|
||||
@GetMapping("/list")
|
||||
fun getMemberList(pageable: Pageable) = ApiResponse.ok(service.getMemberList(pageable))
|
||||
|
||||
@GetMapping("/search")
|
||||
fun searchMember(
|
||||
@RequestParam(value = "search_word") searchWord: String,
|
||||
pageable: Pageable
|
||||
) = ApiResponse.ok(service.searchMember(searchWord, pageable))
|
||||
|
||||
@GetMapping("/creator/all/list")
|
||||
fun getCreatorAllList() = ApiResponse.ok(service.getCreatorAllList())
|
||||
|
||||
@GetMapping("/creator/list")
|
||||
fun getCreatorList(pageable: Pageable) = ApiResponse.ok(service.getCreatorList(pageable))
|
||||
|
||||
@GetMapping("/creator/search")
|
||||
fun searchCreator(
|
||||
@RequestParam(value = "search_word") searchWord: String,
|
||||
pageable: Pageable
|
||||
) = ApiResponse.ok(service.searchCreator(searchWord, pageable))
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package kr.co.vividnext.sodalive.admin.member
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
||||
interface AdminMemberRepository : JpaRepository<Member, Long>, AdminMemberQueryRepository
|
||||
|
||||
interface AdminMemberQueryRepository {
|
||||
fun getMemberTotalCount(role: MemberRole? = null): Int
|
||||
fun getMemberList(offset: Long, limit: Long, role: MemberRole? = null): List<Member>
|
||||
fun searchMember(searchWord: String, offset: Long, limit: Long, role: MemberRole? = null): List<Member>
|
||||
|
||||
fun searchMemberTotalCount(searchWord: String, role: MemberRole? = null): Int
|
||||
fun getCreatorAllList(): List<GetAdminCreatorAllListResponse>
|
||||
}
|
||||
|
||||
class AdminMemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : AdminMemberQueryRepository {
|
||||
override fun getMemberList(offset: Long, limit: Long, role: MemberRole?): List<Member> {
|
||||
return queryFactory
|
||||
.selectFrom(member)
|
||||
.where(
|
||||
member.role.ne(MemberRole.ADMIN)
|
||||
.and(
|
||||
if (role != null) {
|
||||
member.role.eq(role)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.orderBy(member.id.desc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun getMemberTotalCount(role: MemberRole?): Int {
|
||||
return queryFactory
|
||||
.select(member.id)
|
||||
.from(member)
|
||||
.where(
|
||||
member.role.ne(MemberRole.ADMIN)
|
||||
.and(
|
||||
if (role != null) {
|
||||
member.role.eq(role)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
)
|
||||
.fetch()
|
||||
.size
|
||||
}
|
||||
|
||||
override fun searchMember(searchWord: String, offset: Long, limit: Long, role: MemberRole?): List<Member> {
|
||||
return queryFactory
|
||||
.selectFrom(member)
|
||||
.where(
|
||||
member.nickname.contains(searchWord)
|
||||
.or(member.email.contains(searchWord))
|
||||
.and(
|
||||
if (role != null) {
|
||||
member.role.eq(role)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.orderBy(member.id.desc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun searchMemberTotalCount(searchWord: String, role: MemberRole?): Int {
|
||||
return queryFactory
|
||||
.select(member.id)
|
||||
.from(member)
|
||||
.where(
|
||||
member.nickname.contains(searchWord)
|
||||
.or(member.email.contains(searchWord))
|
||||
.and(
|
||||
if (role != null) {
|
||||
member.role.eq(role)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
)
|
||||
.fetch()
|
||||
.size
|
||||
}
|
||||
|
||||
override fun getCreatorAllList(): List<GetAdminCreatorAllListResponse> {
|
||||
return queryFactory
|
||||
.select(
|
||||
QGetAdminCreatorAllListResponse(
|
||||
member.id,
|
||||
member.nickname
|
||||
)
|
||||
)
|
||||
.from(member)
|
||||
.where(
|
||||
member.role.eq(MemberRole.CREATOR)
|
||||
.and(member.isActive.isTrue)
|
||||
)
|
||||
.fetch()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
package kr.co.vividnext.sodalive.admin.member
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Service
|
||||
class AdminMemberService(
|
||||
private val repository: AdminMemberRepository,
|
||||
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) {
|
||||
fun getMemberList(pageable: Pageable): GetAdminMemberListResponse {
|
||||
val totalCount = repository.getMemberTotalCount()
|
||||
val memberList = processMemberListToGetAdminMemberListResponseItemList(
|
||||
memberList = repository.getMemberList(
|
||||
offset = pageable.offset,
|
||||
limit = pageable.pageSize.toLong()
|
||||
)
|
||||
)
|
||||
|
||||
return GetAdminMemberListResponse(totalCount, memberList)
|
||||
}
|
||||
|
||||
fun searchMember(searchWord: String, pageable: Pageable): GetAdminMemberListResponse {
|
||||
if (searchWord.length < 2) throw SodaException("2글자 이상 입력하세요.")
|
||||
val totalCount = repository.searchMemberTotalCount(searchWord = searchWord)
|
||||
val memberList = processMemberListToGetAdminMemberListResponseItemList(
|
||||
memberList = repository.searchMember(
|
||||
searchWord = searchWord,
|
||||
offset = pageable.offset,
|
||||
limit = pageable.pageSize.toLong()
|
||||
)
|
||||
)
|
||||
|
||||
return GetAdminMemberListResponse(totalCount, memberList)
|
||||
}
|
||||
|
||||
fun getCreatorList(pageable: Pageable): GetAdminMemberListResponse {
|
||||
val totalCount = repository.getMemberTotalCount(role = MemberRole.CREATOR)
|
||||
val creatorList = processMemberListToGetAdminMemberListResponseItemList(
|
||||
memberList = repository.getMemberList(
|
||||
offset = pageable.offset,
|
||||
limit = pageable.pageSize.toLong(),
|
||||
role = MemberRole.CREATOR
|
||||
)
|
||||
)
|
||||
|
||||
return GetAdminMemberListResponse(totalCount, creatorList)
|
||||
}
|
||||
|
||||
fun searchCreator(searchWord: String, pageable: Pageable): GetAdminMemberListResponse {
|
||||
if (searchWord.length < 2) throw SodaException("2글자 이상 입력하세요.")
|
||||
val totalCount = repository.searchMemberTotalCount(searchWord = searchWord, role = MemberRole.CREATOR)
|
||||
val creatorList = processMemberListToGetAdminMemberListResponseItemList(
|
||||
memberList = repository.searchMember(
|
||||
searchWord = searchWord,
|
||||
offset = pageable.offset,
|
||||
limit = pageable.pageSize.toLong(),
|
||||
role = MemberRole.CREATOR
|
||||
)
|
||||
)
|
||||
|
||||
return GetAdminMemberListResponse(totalCount, creatorList)
|
||||
}
|
||||
|
||||
private fun processMemberListToGetAdminMemberListResponseItemList(
|
||||
memberList: List<Member>
|
||||
): List<GetAdminMemberListResponseItem> {
|
||||
return memberList
|
||||
.asSequence()
|
||||
.map {
|
||||
val userType = when (it.role) {
|
||||
MemberRole.ADMIN -> "관리자"
|
||||
MemberRole.USER -> "일반회원"
|
||||
MemberRole.CREATOR -> "크리에이터"
|
||||
MemberRole.AGENT -> "에이전트"
|
||||
MemberRole.BOT -> "봇"
|
||||
}
|
||||
|
||||
val signUpDate = it.createdAt!!
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of("Asia/Seoul"))
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
||||
|
||||
val signOutDate = if (it.signOutReasons.isNotEmpty()) {
|
||||
it.signOutReasons.last().createdAt!!
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of("Asia/Seoul"))
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
GetAdminMemberListResponseItem(
|
||||
id = it.id!!,
|
||||
email = it.email,
|
||||
nickname = it.nickname,
|
||||
profileUrl = if (it.profileImage != null) {
|
||||
"$cloudFrontHost/${it.profileImage}"
|
||||
} else {
|
||||
"$cloudFrontHost/profile/default-profile.png"
|
||||
},
|
||||
userType = userType,
|
||||
container = it.container,
|
||||
auth = it.auth != null,
|
||||
signUpDate = signUpDate,
|
||||
signOutDate = signOutDate,
|
||||
isActive = it.isActive
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
}
|
||||
|
||||
fun getCreatorAllList(): List<GetAdminCreatorAllListResponse> {
|
||||
return repository.getCreatorAllList()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package kr.co.vividnext.sodalive.admin.member
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
|
||||
data class GetAdminCreatorAllListResponse @QueryProjection constructor(
|
||||
val id: Long,
|
||||
val nickname: String
|
||||
)
|
|
@ -0,0 +1,19 @@
|
|||
package kr.co.vividnext.sodalive.admin.member
|
||||
|
||||
data class GetAdminMemberListResponse(
|
||||
val totalCount: Int,
|
||||
val items: List<GetAdminMemberListResponseItem>
|
||||
)
|
||||
|
||||
data class GetAdminMemberListResponseItem(
|
||||
val id: Long,
|
||||
val email: String,
|
||||
val nickname: String,
|
||||
val profileUrl: String,
|
||||
val userType: String,
|
||||
val container: String,
|
||||
val auth: Boolean,
|
||||
val signUpDate: String,
|
||||
val signOutDate: String,
|
||||
val isActive: Boolean
|
||||
)
|
|
@ -0,0 +1,39 @@
|
|||
package kr.co.vividnext.sodalive.admin.member.tag
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
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.RequestPart
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/member/tag")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
class AdminMemberTagController(private val service: AdminMemberTagService) {
|
||||
@PostMapping
|
||||
fun enrollmentCreatorTag(
|
||||
@RequestPart("image") image: MultipartFile,
|
||||
@RequestPart("request") requestString: String
|
||||
) = ApiResponse.ok(service.uploadTagImage(image, requestString), "등록되었습니다.")
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
fun deleteCreatorTag(@PathVariable id: Long) = ApiResponse.ok(service.deleteTag(id), "삭제되었습니다.")
|
||||
|
||||
@PutMapping("/{id}")
|
||||
fun modifyCreatorTag(
|
||||
@PathVariable id: Long,
|
||||
@RequestPart("image") image: MultipartFile?,
|
||||
@RequestPart("request") requestString: String
|
||||
) = ApiResponse.ok(service.modifyTag(id, image, requestString), "수정되었습니다.")
|
||||
|
||||
@PutMapping("/orders")
|
||||
fun updateTagOrders(
|
||||
@RequestBody request: UpdateTagOrdersRequest
|
||||
) = ApiResponse.ok(service.updateTagOrders(request.ids), "수정되었습니다.")
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package kr.co.vividnext.sodalive.admin.member.tag
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.member.tag.CreatorTag
|
||||
import kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
||||
interface AdminMemberTagRepository : JpaRepository<CreatorTag, Long>, AdminMemberTagQueryRepository
|
||||
|
||||
interface AdminMemberTagQueryRepository {
|
||||
fun findByTag(tag: String): Long?
|
||||
}
|
||||
|
||||
class AdminMemberTagQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : AdminMemberTagQueryRepository {
|
||||
override fun findByTag(tag: String): Long? {
|
||||
return queryFactory
|
||||
.select(creatorTag.id)
|
||||
.from(creatorTag)
|
||||
.where(creatorTag.tag.eq(tag))
|
||||
.fetchFirst()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package kr.co.vividnext.sodalive.admin.member.tag
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.tag.CreatorTag
|
||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@Service
|
||||
class AdminMemberTagService(
|
||||
private val repository: AdminMemberTagRepository,
|
||||
private val s3Uploader: S3Uploader,
|
||||
private val objectMapper: ObjectMapper,
|
||||
|
||||
@Value("\${cloud.aws.s3.bucket}")
|
||||
private val bucket: String
|
||||
) {
|
||||
@Transactional
|
||||
fun uploadTagImage(image: MultipartFile, requestString: String) {
|
||||
val request = objectMapper.readValue(requestString, CreateMemberTagRequest::class.java)
|
||||
tagExistCheck(request)
|
||||
|
||||
val fileName = generateFileName()
|
||||
val imagePath = s3Uploader.upload(
|
||||
inputStream = image.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "creator_tag/$fileName"
|
||||
)
|
||||
return createTag(request.tag, imagePath)
|
||||
}
|
||||
|
||||
private fun tagExistCheck(request: CreateMemberTagRequest) {
|
||||
repository.findByTag(request.tag)?.let { throw SodaException("이미 등록된 태그 입니다.") }
|
||||
}
|
||||
|
||||
private fun createTag(tag: String, imagePath: String) {
|
||||
repository.save(CreatorTag(tag, imagePath))
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun deleteTag(id: Long) {
|
||||
val creatorTag = repository.findByIdOrNull(id)
|
||||
?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
creatorTag.tag = "${creatorTag.tag}_deleted"
|
||||
creatorTag.isActive = false
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun modifyTag(id: Long, image: MultipartFile?, requestString: String) {
|
||||
val creatorTag = repository.findByIdOrNull(id)
|
||||
?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
val request = objectMapper.readValue(requestString, CreateMemberTagRequest::class.java)
|
||||
creatorTag.tag = request.tag
|
||||
|
||||
if (image != null) {
|
||||
val fileName = generateFileName()
|
||||
val imagePath = s3Uploader.upload(
|
||||
inputStream = image.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "creator_tag/$fileName"
|
||||
)
|
||||
creatorTag.image = imagePath
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateTagOrders(ids: List<Long>) {
|
||||
for (index in ids.indices) {
|
||||
val tag = repository.findByIdOrNull(ids[index])
|
||||
|
||||
if (tag != null) {
|
||||
tag.orders = index + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package kr.co.vividnext.sodalive.admin.member.tag
|
||||
|
||||
data class CreateMemberTagRequest(val tag: String)
|
|
@ -0,0 +1,3 @@
|
|||
package kr.co.vividnext.sodalive.admin.member.tag
|
||||
|
||||
data class UpdateTagOrdersRequest(val ids: List<Long>)
|
|
@ -1,13 +1,56 @@
|
|||
package kr.co.vividnext.sodalive.event
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/event")
|
||||
class EventController(private val service: EventService) {
|
||||
@GetMapping
|
||||
fun getEventList() = ApiResponse.ok(service.getEventList())
|
||||
|
||||
@GetMapping("/popup")
|
||||
fun getEventPopup() = ApiResponse.ok(service.getEventPopup())
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun createEvent(
|
||||
@RequestParam("thumbnail") thumbnail: MultipartFile,
|
||||
@RequestParam(value = "detail", required = false) detail: MultipartFile? = null,
|
||||
@RequestParam(value = "popup", required = false) popup: MultipartFile? = null,
|
||||
@RequestParam(value = "link", required = false) link: String? = null,
|
||||
@RequestParam(value = "title", required = false) title: String? = null,
|
||||
@RequestParam(value = "isPopup") isPopup: Boolean
|
||||
) = ApiResponse.ok(
|
||||
service.save(thumbnail, detail, popup, link, title, isPopup),
|
||||
"등록되었습니다."
|
||||
)
|
||||
|
||||
@PutMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun updateEvent(
|
||||
@RequestParam(value = "id") id: Long,
|
||||
@RequestParam(value = "thumbnail", required = false) thumbnail: MultipartFile? = null,
|
||||
@RequestParam(value = "detail", required = false) detail: MultipartFile? = null,
|
||||
@RequestParam(value = "popup", required = false) popup: MultipartFile? = null,
|
||||
@RequestParam(value = "link", required = false) link: String? = null,
|
||||
@RequestParam(value = "title", required = false) title: String? = null,
|
||||
@RequestParam(value = "isPopup", required = false) isPopup: Boolean? = null
|
||||
) = ApiResponse.ok(
|
||||
service.update(id, thumbnail, detail, popup, link, title, isPopup),
|
||||
"수정되었습니다."
|
||||
)
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun deleteEvent(@PathVariable id: Long) = ApiResponse.ok(service.delete(id), "삭제되었습니다.")
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ interface EventRepository : JpaRepository<Event, Long>, EventQueryRepository
|
|||
|
||||
interface EventQueryRepository {
|
||||
fun getEventList(): List<EventItem>
|
||||
fun getMainEventPopup(): EventItem?
|
||||
}
|
||||
|
||||
@Repository
|
||||
|
@ -32,4 +33,27 @@ class EventQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Even
|
|||
.orderBy(event.id.desc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun getMainEventPopup(): EventItem? {
|
||||
return queryFactory
|
||||
.select(
|
||||
QEventItem(
|
||||
event.id,
|
||||
event.title,
|
||||
event.thumbnailImage,
|
||||
event.detailImage,
|
||||
event.popupImage,
|
||||
event.link,
|
||||
event.isPopup
|
||||
)
|
||||
)
|
||||
.from(event)
|
||||
.where(
|
||||
event.isActive.isTrue
|
||||
.and(event.isPopup.isTrue)
|
||||
.and(event.popupImage.isNotNull)
|
||||
)
|
||||
.orderBy(event.id.desc())
|
||||
.fetchFirst()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
package kr.co.vividnext.sodalive.event
|
||||
|
||||
import com.amazonaws.services.s3.model.ObjectMetadata
|
||||
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@Service
|
||||
class EventService(
|
||||
private val repository: EventRepository,
|
||||
private val s3Uploader: S3Uploader,
|
||||
|
||||
@Value("\${cloud.aws.s3.bucket}")
|
||||
private val bucket: String,
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) {
|
||||
|
@ -32,4 +42,164 @@ class EventService(
|
|||
|
||||
return GetEventResponse(0, eventList)
|
||||
}
|
||||
|
||||
fun getEventPopup(): EventItem? {
|
||||
val eventPopup = repository.getMainEventPopup()
|
||||
|
||||
if (eventPopup != null) {
|
||||
if (!eventPopup.thumbnailImageUrl.startsWith("https://")) {
|
||||
eventPopup.thumbnailImageUrl = "$cloudFrontHost/${eventPopup.thumbnailImageUrl}"
|
||||
}
|
||||
|
||||
if (eventPopup.detailImageUrl != null && !eventPopup.detailImageUrl!!.startsWith("https://")) {
|
||||
eventPopup.detailImageUrl = "$cloudFrontHost/${eventPopup.detailImageUrl}"
|
||||
}
|
||||
|
||||
if (eventPopup.popupImageUrl != null && !eventPopup.popupImageUrl!!.startsWith("https://")) {
|
||||
eventPopup.popupImageUrl = "$cloudFrontHost/${eventPopup.popupImageUrl}"
|
||||
}
|
||||
}
|
||||
|
||||
return eventPopup
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun save(
|
||||
thumbnail: MultipartFile,
|
||||
detail: MultipartFile? = null,
|
||||
popup: MultipartFile? = null,
|
||||
link: String? = null,
|
||||
title: String? = null,
|
||||
isPopup: Boolean
|
||||
): Long {
|
||||
if (detail == null && link.isNullOrBlank()) throw SodaException("상세이미지 혹은 링크를 등록하세요")
|
||||
|
||||
val event = repository.save(
|
||||
Event(
|
||||
thumbnailImage = "",
|
||||
detailImage = null,
|
||||
popupImage = null,
|
||||
link = link,
|
||||
title = title,
|
||||
isPopup = isPopup
|
||||
)
|
||||
)
|
||||
|
||||
var metadata = ObjectMetadata()
|
||||
metadata.contentLength = thumbnail.size
|
||||
val thumbnailImagePath = s3Uploader.upload(
|
||||
inputStream = thumbnail.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "event/${event.id}/${generateFileName()}",
|
||||
metadata = metadata
|
||||
)
|
||||
|
||||
val detailImagePath = if (detail != null) {
|
||||
metadata = ObjectMetadata()
|
||||
metadata.contentLength = detail.size
|
||||
|
||||
s3Uploader.upload(
|
||||
inputStream = detail.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "event/${event.id}/${generateFileName()}",
|
||||
metadata = metadata
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val popupImagePath = if (popup != null) {
|
||||
metadata = ObjectMetadata()
|
||||
metadata.contentLength = popup.size
|
||||
|
||||
s3Uploader.upload(
|
||||
inputStream = popup.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "event/${event.id}/${generateFileName()}",
|
||||
metadata = metadata
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
event.thumbnailImage = thumbnailImagePath
|
||||
event.detailImage = detailImagePath
|
||||
event.popupImage = popupImagePath
|
||||
|
||||
return event.id ?: throw SodaException("이벤트 등록을 하지 못했습니다.")
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun update(
|
||||
id: Long,
|
||||
thumbnail: MultipartFile? = null,
|
||||
detail: MultipartFile? = null,
|
||||
popup: MultipartFile? = null,
|
||||
link: String? = null,
|
||||
title: String? = null,
|
||||
isPopup: Boolean? = null
|
||||
) {
|
||||
if (id <= 0) throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
if (thumbnail == null && detail == null && link.isNullOrBlank() && title.isNullOrBlank()) {
|
||||
throw SodaException("수정할 내용을 입력하세요.")
|
||||
}
|
||||
|
||||
val event = repository.findByIdOrNull(id)
|
||||
?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
if (thumbnail != null) {
|
||||
val metadata = ObjectMetadata()
|
||||
metadata.contentLength = thumbnail.size
|
||||
|
||||
event.thumbnailImage = s3Uploader.upload(
|
||||
inputStream = thumbnail.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "event/${event.id}/${generateFileName()}"
|
||||
)
|
||||
}
|
||||
|
||||
if (detail != null) {
|
||||
val metadata = ObjectMetadata()
|
||||
metadata.contentLength = detail.size
|
||||
|
||||
event.detailImage = s3Uploader.upload(
|
||||
inputStream = detail.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "event/${event.id}/${generateFileName()}"
|
||||
)
|
||||
}
|
||||
|
||||
if (popup != null) {
|
||||
val metadata = ObjectMetadata()
|
||||
metadata.contentLength = popup.size
|
||||
|
||||
event.popupImage = s3Uploader.upload(
|
||||
inputStream = popup.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "event/${event.id}/${generateFileName()}"
|
||||
)
|
||||
}
|
||||
|
||||
if (!link.isNullOrBlank() && event.link != link) {
|
||||
event.link = link
|
||||
}
|
||||
|
||||
if (!title.isNullOrBlank() && event.title != title) {
|
||||
event.title = title
|
||||
}
|
||||
|
||||
if (isPopup != null) {
|
||||
event.isPopup = isPopup
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun delete(id: Long) {
|
||||
if (id <= 0) throw SodaException("잘못된 요청입니다.")
|
||||
val event = repository.findByIdOrNull(id)
|
||||
?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
event.isActive = false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package kr.co.vividnext.sodalive.extensions
|
||||
|
||||
import java.text.DecimalFormat
|
||||
|
||||
fun Int.moneyFormat(): String = DecimalFormat("###,###").format(this)
|
|
@ -0,0 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.faq
|
||||
|
||||
data class CreateFaqRequest(
|
||||
val question: String,
|
||||
val answer: String,
|
||||
val category: String
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
package kr.co.vividnext.sodalive.faq
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.FetchType
|
||||
import javax.persistence.JoinColumn
|
||||
import javax.persistence.ManyToOne
|
||||
|
||||
@Entity
|
||||
data class Faq(
|
||||
@Column(nullable = false)
|
||||
var question: String,
|
||||
@Column(columnDefinition = "TEXT", nullable = false)
|
||||
var answer: String,
|
||||
var isActive: Boolean = true,
|
||||
val orders: Int = 1
|
||||
) : BaseEntity() {
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "category_id", nullable = false)
|
||||
var category: FaqCategory? = null
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package kr.co.vividnext.sodalive.faq
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
|
||||
@Entity
|
||||
data class FaqCategory(
|
||||
@Column(nullable = false)
|
||||
val category: String,
|
||||
val isActive: Boolean = true,
|
||||
val orders: Int = 1
|
||||
) : BaseEntity()
|
|
@ -0,0 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.faq
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface FaqCategoryRepository : JpaRepository<FaqCategory, Long>
|
|
@ -0,0 +1,41 @@
|
|||
package kr.co.vividnext.sodalive.faq
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/faq")
|
||||
class FaqController(private val service: FaqService) {
|
||||
@PostMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun createFaq(@RequestBody request: CreateFaqRequest) = ApiResponse.ok(
|
||||
service.save(request),
|
||||
"등록되었습니다."
|
||||
)
|
||||
|
||||
@PutMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun modifyFaq(@RequestBody request: ModifyFaqRequest) = ApiResponse.ok(
|
||||
service.modify(request),
|
||||
"수정되었습니다."
|
||||
)
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun deleteCoin(@PathVariable id: Long) = ApiResponse.ok(service.delete(id), "삭제되었습니다.")
|
||||
|
||||
@GetMapping
|
||||
fun getFaqList(@RequestParam("category") category: String) = ApiResponse.ok(service.getFaqList(category))
|
||||
|
||||
@GetMapping("/category")
|
||||
fun getFaqCategoryList() = ApiResponse.ok(service.getCategoryList())
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package kr.co.vividnext.sodalive.faq
|
||||
|
||||
import com.querydsl.core.types.dsl.Expressions
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.faq.QFaq.faq
|
||||
import kr.co.vividnext.sodalive.faq.QFaqCategory.faqCategory
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface FaqRepository : JpaRepository<Faq, Long>
|
||||
|
||||
@Repository
|
||||
class FaqQueryRepository(private val queryFactory: JPAQueryFactory) {
|
||||
fun getCategory(category: String): FaqCategory? {
|
||||
return queryFactory
|
||||
.selectFrom(faqCategory)
|
||||
.where(
|
||||
faqCategory.isActive.isTrue
|
||||
.and(faqCategory.category.eq(category))
|
||||
)
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
fun getCategoryList(): List<String> {
|
||||
return queryFactory
|
||||
.select(faqCategory.category)
|
||||
.from(faqCategory)
|
||||
.where(faqCategory.isActive.isTrue)
|
||||
.orderBy(faqCategory.orders.desc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun getFaqList(category: String): List<GetFaqResponseItem> {
|
||||
return queryFactory
|
||||
.select(
|
||||
QGetFaqResponseItem(
|
||||
faq.id,
|
||||
Expressions.asString(category),
|
||||
faq.question,
|
||||
faq.answer
|
||||
)
|
||||
)
|
||||
.from(faq)
|
||||
.innerJoin(faq.category, faqCategory)
|
||||
.where(
|
||||
faq.isActive.isTrue
|
||||
.and(faqCategory.isActive.isTrue)
|
||||
.and(faqCategory.category.eq(category))
|
||||
)
|
||||
.orderBy(faq.orders.desc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun getFaq(id: Long): Faq? {
|
||||
return queryFactory
|
||||
.selectFrom(faq)
|
||||
.where(
|
||||
faq.isActive.isTrue
|
||||
.and(faq.id.eq(id))
|
||||
)
|
||||
.fetchFirst()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package kr.co.vividnext.sodalive.faq
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@Service
|
||||
class FaqService(
|
||||
private val repository: FaqRepository,
|
||||
private val queryRepository: FaqQueryRepository
|
||||
) {
|
||||
@Transactional
|
||||
fun save(request: CreateFaqRequest): Long {
|
||||
if (request.question.isBlank()) throw SodaException("질문을 입력하세요.")
|
||||
if (request.answer.isBlank()) throw SodaException("답변을 입력하세요.")
|
||||
if (request.category.isBlank()) throw SodaException("카테고리를 선택하세요.")
|
||||
|
||||
val category = queryRepository.getCategory(request.category)
|
||||
?: throw SodaException("잘못된 카테고리 입니다.")
|
||||
|
||||
val faq = Faq(request.question, request.answer)
|
||||
faq.category = category
|
||||
|
||||
return repository.save(faq).id!!
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun modify(request: ModifyFaqRequest) {
|
||||
val faq = queryRepository.getFaq(request.id)
|
||||
?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
if (request.question != null) {
|
||||
if (request.question.isBlank()) throw SodaException("질문을 입력하세요.")
|
||||
faq.question = request.question
|
||||
}
|
||||
|
||||
if (request.answer != null) {
|
||||
if (request.answer.isBlank()) throw SodaException("답변을 입력하세요.")
|
||||
faq.answer = request.answer
|
||||
}
|
||||
|
||||
if (request.category != null) {
|
||||
if (request.category.isBlank()) throw SodaException("카테고리를 선택하세요.")
|
||||
val category = queryRepository.getCategory(request.category) ?: throw SodaException("잘못된 카테고리 입니다.")
|
||||
faq.category = category
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun delete(id: Long) {
|
||||
if (id <= 0) throw SodaException("잘못된 요청입니다.")
|
||||
val faq = repository.findByIdOrNull(id)
|
||||
?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
faq.isActive = false
|
||||
}
|
||||
|
||||
fun getFaqList(category: String) = queryRepository.getFaqList(category)
|
||||
|
||||
fun getCategoryList() = queryRepository.getCategoryList()
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package kr.co.vividnext.sodalive.faq
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
|
||||
data class GetFaqResponseItem @QueryProjection constructor(
|
||||
val id: Long,
|
||||
val category: String,
|
||||
val question: String,
|
||||
val answer: String
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
package kr.co.vividnext.sodalive.faq
|
||||
|
||||
data class ModifyFaqRequest(
|
||||
val id: Long,
|
||||
val question: String? = null,
|
||||
val answer: String? = null,
|
||||
val category: String? = null
|
||||
)
|
|
@ -11,8 +11,6 @@ import javax.persistence.ManyToOne
|
|||
|
||||
@Entity
|
||||
data class RecommendLiveCreatorBanner(
|
||||
@Column(nullable = false)
|
||||
var image: String,
|
||||
@Column(nullable = false)
|
||||
var startDate: LocalDateTime,
|
||||
@Column(nullable = false)
|
||||
|
@ -20,7 +18,9 @@ data class RecommendLiveCreatorBanner(
|
|||
@Column(nullable = false)
|
||||
var isAdult: Boolean = false,
|
||||
@Column(nullable = false)
|
||||
var orders: Int = 1
|
||||
var orders: Int = 1,
|
||||
@Column(nullable = true)
|
||||
var image: String? = null
|
||||
) : BaseEntity() {
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "creator_id", nullable = false)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.live.recommend
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface RecommendLiveCreatorBannerRepository : JpaRepository<RecommendLiveCreatorBanner, Long>
|
|
@ -792,7 +792,7 @@ class LiveRoomService(
|
|||
val host = room.member ?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
if (host.role != MemberRole.CREATOR) {
|
||||
throw SodaException("비비드넥스트와 계약한\n요즘친구에게만 후원을 하실 수 있습니다.")
|
||||
throw SodaException("비비드넥스트와 계약한\n크리에이터에게만 후원을 하실 수 있습니다.")
|
||||
}
|
||||
|
||||
canPaymentService.spendCan(
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
package kr.co.vividnext.sodalive.live.tag
|
||||
|
||||
import kr.co.vividnext.sodalive.admin.member.tag.UpdateTagOrdersRequest
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestPart
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
@ -22,6 +27,24 @@ class LiveTagController(private val service: LiveTagService) {
|
|||
@RequestPart("request") requestString: String
|
||||
) = ApiResponse.ok(service.enrollmentLiveTag(image, requestString), "등록되었습니다.")
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun deleteSudaTag(@PathVariable id: Long) = ApiResponse.ok(service.deleteTag(id), "삭제되었습니다.")
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun modifySudaTag(
|
||||
@PathVariable id: Long,
|
||||
@RequestPart("image") image: MultipartFile?,
|
||||
@RequestPart("request") requestString: String
|
||||
) = ApiResponse.ok(service.modifyTag(id, image, requestString), "수정되었습니다.")
|
||||
|
||||
@PutMapping("/orders")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun updateTagOrders(
|
||||
@RequestBody request: UpdateTagOrdersRequest
|
||||
) = ApiResponse.ok(service.updateTagOrders(request.ids), "수정되었습니다.")
|
||||
|
||||
@GetMapping
|
||||
fun getTags(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
|
|
|
@ -7,7 +7,9 @@ import kr.co.vividnext.sodalive.common.SodaException
|
|||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@Service
|
||||
|
@ -22,6 +24,7 @@ class LiveTagService(
|
|||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) {
|
||||
@Transactional
|
||||
fun enrollmentLiveTag(image: MultipartFile, requestString: String) {
|
||||
val request = objectMapper.readValue(requestString, CreateLiveTagRequest::class.java)
|
||||
tagExistCheck(request)
|
||||
|
@ -42,6 +45,50 @@ class LiveTagService(
|
|||
tag.image = tagImagePath
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun deleteTag(id: Long) {
|
||||
val tag = repository.findByIdOrNull(id)
|
||||
?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
tag.tag = "${tag.tag}_deleted"
|
||||
tag.isActive = false
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun modifyTag(id: Long, image: MultipartFile?, requestString: String) {
|
||||
val tag = repository.findByIdOrNull(id)
|
||||
?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
val request = objectMapper.readValue(requestString, CreateLiveTagRequest::class.java)
|
||||
tag.tag = request.tag
|
||||
|
||||
if (image != null) {
|
||||
val metadata = ObjectMetadata()
|
||||
metadata.contentLength = image.size
|
||||
|
||||
val tagImageFileName = generateFileName(prefix = "${tag.id}-")
|
||||
val tagImagePath = s3Uploader.upload(
|
||||
inputStream = image.inputStream,
|
||||
bucket = coverImageBucket,
|
||||
filePath = "live_cover/${tag.id}/$tagImageFileName",
|
||||
metadata = metadata
|
||||
)
|
||||
|
||||
tag.image = tagImagePath
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateTagOrders(ids: List<Long>) {
|
||||
for (index in ids.indices) {
|
||||
val tag = repository.findByIdOrNull(ids[index])
|
||||
|
||||
if (tag != null) {
|
||||
tag.orders = index + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getTags(member: Member): List<GetLiveTagResponse> {
|
||||
return repository.getTags(role = member.role, isAdult = member.auth != null, cloudFrontHost = cloudFrontHost)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ interface MemberRepository : JpaRepository<Member, Long>, MemberQueryRepository
|
|||
interface MemberQueryRepository {
|
||||
fun findByPushToken(pushToken: String): List<Member>
|
||||
fun findByNicknameAndOtherCondition(nickname: String, memberId: Long): List<Member>
|
||||
fun findCreatorByIdOrNull(memberId: Long): Member?
|
||||
}
|
||||
|
||||
@Repository
|
||||
|
@ -36,4 +37,14 @@ class MemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Mem
|
|||
)
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun findCreatorByIdOrNull(memberId: Long): Member? {
|
||||
return queryFactory
|
||||
.selectFrom(member)
|
||||
.where(
|
||||
member.id.eq(memberId)
|
||||
.and(member.role.eq(MemberRole.CREATOR))
|
||||
)
|
||||
.fetchFirst()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package kr.co.vividnext.sodalive.member.stipulation
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/stplat")
|
||||
class StipulationController(private val service: StipulationService) {
|
||||
@PostMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun enrollment(@RequestBody request: StipulationDto) = ApiResponse.ok(
|
||||
service.enrollment(request),
|
||||
"등록되었습니다."
|
||||
)
|
||||
|
||||
@PostMapping("/modify")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun modify(@RequestBody request: StipulationModifyRequest) = ApiResponse.ok(
|
||||
service.modify(request),
|
||||
"수정되었습니다."
|
||||
)
|
||||
|
||||
@GetMapping("/terms_of_service")
|
||||
fun getTermsOfService() = ApiResponse.ok(service.getTermsOfService())
|
||||
|
||||
@GetMapping("/privacy_policy")
|
||||
fun getPrivacyPolicy() = ApiResponse.ok(service.getPrivacyPolicy())
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.member.stipulation
|
||||
|
||||
data class StipulationDto(
|
||||
val id: Long = 0,
|
||||
val title: String,
|
||||
val description: String
|
||||
) {
|
||||
constructor(stipulation: Stipulation) : this(stipulation.id!!, stipulation.title, stipulation.description)
|
||||
|
||||
fun toEntity() = Stipulation(title, description)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package kr.co.vividnext.sodalive.member.stipulation
|
||||
|
||||
data class StipulationModifyRequest(
|
||||
val id: Long,
|
||||
val description: String
|
||||
)
|
|
@ -0,0 +1,38 @@
|
|||
package kr.co.vividnext.sodalive.member.stipulation
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.stipulation.StipulationIds.PRIVACY_POLICY_ID
|
||||
import kr.co.vividnext.sodalive.member.stipulation.StipulationIds.TERMS_OF_SERVICE_ID
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class StipulationService(private val repository: StipulationRepository) {
|
||||
fun enrollment(request: StipulationDto) {
|
||||
repository.save(request.toEntity())
|
||||
}
|
||||
|
||||
fun getTermsOfService(): StipulationDto {
|
||||
val stipulation = repository.findByIdOrNull(TERMS_OF_SERVICE_ID)
|
||||
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
|
||||
|
||||
return StipulationDto(stipulation)
|
||||
}
|
||||
|
||||
fun getPrivacyPolicy(): StipulationDto {
|
||||
val stipulation = repository.findByIdOrNull(PRIVACY_POLICY_ID)
|
||||
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
|
||||
|
||||
return StipulationDto(stipulation)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun modify(request: StipulationModifyRequest) {
|
||||
val stipulation = repository.findByIdOrNull(request.id)
|
||||
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
|
||||
|
||||
stipulation.description = request.description
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.member.tag
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
|
||||
data class GetMemberTagResponse @QueryProjection constructor(
|
||||
val id: Long,
|
||||
val tag: String,
|
||||
val image: String
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package kr.co.vividnext.sodalive.member.tag
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/member/tag")
|
||||
class MemberTagController(private val service: MemberTagService) {
|
||||
@GetMapping
|
||||
fun getTags() = ApiResponse.ok(service.getTags())
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package kr.co.vividnext.sodalive.member.tag
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
||||
interface MemberTagRepository : JpaRepository<CreatorTag, Long>, MemberTagQueryRepository
|
||||
|
||||
interface MemberTagQueryRepository {
|
||||
fun getTags(): List<GetMemberTagResponse>
|
||||
}
|
||||
|
||||
class MemberTagQueryRepositoryImpl(
|
||||
private val queryFactory: JPAQueryFactory,
|
||||
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) : MemberTagQueryRepository {
|
||||
override fun getTags(): List<GetMemberTagResponse> {
|
||||
return queryFactory
|
||||
.select(
|
||||
QGetMemberTagResponse(
|
||||
creatorTag.id,
|
||||
creatorTag.tag,
|
||||
creatorTag.image.prepend("/").prepend(cloudFrontHost)
|
||||
)
|
||||
)
|
||||
.from(creatorTag)
|
||||
.where(creatorTag.isActive.eq(true))
|
||||
.orderBy(creatorTag.orders.asc())
|
||||
.fetch()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package kr.co.vividnext.sodalive.member.tag
|
||||
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class MemberTagService(private val repository: MemberTagRepository) {
|
||||
fun getTags(): List<GetMemberTagResponse> {
|
||||
return repository.getTags()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue