| @@ -0,0 +1,45 @@ | |||||||
|  | package kr.co.vividnext.sodalive.admin.live.signature | ||||||
|  |  | ||||||
|  | import com.querydsl.jpa.impl.JPAQueryFactory | ||||||
|  | import kr.co.vividnext.sodalive.live.signature.QSignatureCan.signatureCan | ||||||
|  | import kr.co.vividnext.sodalive.live.signature.SignatureCan | ||||||
|  | import kr.co.vividnext.sodalive.member.QMember.member | ||||||
|  | import org.springframework.data.jpa.repository.JpaRepository | ||||||
|  |  | ||||||
|  | interface AdminAdminSignatureCanRepository : JpaRepository<SignatureCan, Long>, AdminSignatureCanQueryRepository | ||||||
|  |  | ||||||
|  | interface AdminSignatureCanQueryRepository { | ||||||
|  |     fun getSignatureCanListTotalCount(): Int | ||||||
|  |  | ||||||
|  |     fun getSignatureCanList(imageHost: String, offset: Long, limit: Long): List<GetSignatureCanListItem> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class AdminSignatureCanQueryRepositoryImpl( | ||||||
|  |     private val queryFactory: JPAQueryFactory | ||||||
|  | ) : AdminSignatureCanQueryRepository { | ||||||
|  |     override fun getSignatureCanListTotalCount(): Int { | ||||||
|  |         return queryFactory.select(signatureCan.id) | ||||||
|  |             .from(signatureCan) | ||||||
|  |             .where(signatureCan.isActive.isTrue) | ||||||
|  |             .fetch() | ||||||
|  |             .size | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun getSignatureCanList(imageHost: String, offset: Long, limit: Long): List<GetSignatureCanListItem> { | ||||||
|  |         return queryFactory.select( | ||||||
|  |             QGetSignatureCanListItem( | ||||||
|  |                 signatureCan.id, | ||||||
|  |                 signatureCan.can, | ||||||
|  |                 signatureCan.image.prepend("/").prepend(imageHost), | ||||||
|  |                 member.nickname | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |             .from(signatureCan) | ||||||
|  |             .innerJoin(signatureCan.creator, member) | ||||||
|  |             .where(signatureCan.isActive.isTrue) | ||||||
|  |             .offset(offset) | ||||||
|  |             .limit(limit) | ||||||
|  |             .orderBy(signatureCan.id.desc()) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,47 @@ | |||||||
|  | package kr.co.vividnext.sodalive.admin.live.signature | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
|  | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | 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.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
|  | import org.springframework.web.bind.annotation.RestController | ||||||
|  | import org.springframework.web.multipart.MultipartFile | ||||||
|  |  | ||||||
|  | @RestController | ||||||
|  | @PreAuthorize("hasRole('ADMIN')") | ||||||
|  | @RequestMapping("/admin/live/signature-can") | ||||||
|  | class AdminSignatureCanController(private val service: AdminSignatureCanService) { | ||||||
|  |     @GetMapping | ||||||
|  |     fun getSignatureCanList(pageable: Pageable) = ApiResponse.ok(data = service.getSignatureCanList(pageable)) | ||||||
|  |  | ||||||
|  |     @PostMapping | ||||||
|  |     fun createSignatureCan( | ||||||
|  |         @RequestParam("can") can: Int, | ||||||
|  |         @RequestParam("image") image: MultipartFile, | ||||||
|  |         @RequestParam("creator_id") creatorId: Long | ||||||
|  |     ) = ApiResponse.ok( | ||||||
|  |         service.createSignatureCan(can = can, creatorId = creatorId, image = image), | ||||||
|  |         "등록되었습니다." | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     @PutMapping | ||||||
|  |     fun modifySignatureCan( | ||||||
|  |         @RequestParam("id") id: Long, | ||||||
|  |         @RequestParam("image", required = false) image: MultipartFile?, | ||||||
|  |         @RequestParam("isActive", required = false) isActive: Boolean? | ||||||
|  |     ) = run { | ||||||
|  |         if (image == null && isActive == null) { | ||||||
|  |             throw SodaException("변경사항이 없습니다.") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.modifySignatureCan(id = id, image = image, isActive = isActive), | ||||||
|  |             "수정되었습니다." | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,84 @@ | |||||||
|  | package kr.co.vividnext.sodalive.admin.live.signature | ||||||
|  |  | ||||||
|  | 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.signature.SignatureCan | ||||||
|  | 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 | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class AdminSignatureCanService( | ||||||
|  |     private val repository: AdminAdminSignatureCanRepository, | ||||||
|  |     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 imageHost: String | ||||||
|  | ) { | ||||||
|  |     fun getSignatureCanList(pageable: Pageable): GetSignatureCanListResponse { | ||||||
|  |         val totalCount = repository.getSignatureCanListTotalCount() | ||||||
|  |         val items = repository.getSignatureCanList( | ||||||
|  |             imageHost = imageHost, | ||||||
|  |             offset = pageable.offset, | ||||||
|  |             limit = pageable.pageSize.toLong() | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         return GetSignatureCanListResponse(totalCount, items) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Transactional | ||||||
|  |     fun createSignatureCan(can: Int, creatorId: Long, image: MultipartFile) { | ||||||
|  |         if (creatorId < 1) throw SodaException("올바른 크리에이터를 선택해 주세요.") | ||||||
|  |  | ||||||
|  |         val creator = memberRepository.findCreatorByIdOrNull(memberId = creatorId) | ||||||
|  |             ?: throw SodaException("올바른 크리에이터를 선택해 주세요.") | ||||||
|  |  | ||||||
|  |         val signatureCan = SignatureCan(can = can) | ||||||
|  |         signatureCan.creator = creator | ||||||
|  |         repository.save(signatureCan) | ||||||
|  |  | ||||||
|  |         val metadata = ObjectMetadata() | ||||||
|  |         metadata.contentLength = image.size | ||||||
|  |         val imagePath = s3Uploader.upload( | ||||||
|  |             inputStream = image.inputStream, | ||||||
|  |             bucket = bucket, | ||||||
|  |             filePath = "signature_can/${signatureCan.id}/${generateFileName()}", | ||||||
|  |             metadata = metadata | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         signatureCan.image = imagePath | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Transactional | ||||||
|  |     fun modifySignatureCan(id: Long, image: MultipartFile?, isActive: Boolean?) { | ||||||
|  |         val signatureCan = repository.findByIdOrNull(id = id) | ||||||
|  |             ?: throw SodaException("잘못된 요청입니다.") | ||||||
|  |  | ||||||
|  |         if (isActive != null) { | ||||||
|  |             signatureCan.isActive = isActive | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (image != null) { | ||||||
|  |             val metadata = ObjectMetadata() | ||||||
|  |             metadata.contentLength = image.size | ||||||
|  |             val imagePath = s3Uploader.upload( | ||||||
|  |                 inputStream = image.inputStream, | ||||||
|  |                 bucket = bucket, | ||||||
|  |                 filePath = "signature_can/${signatureCan.id}/${generateFileName()}", | ||||||
|  |                 metadata = metadata | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |             signatureCan.image = imagePath | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | package kr.co.vividnext.sodalive.admin.live.signature | ||||||
|  |  | ||||||
|  | import com.querydsl.core.annotations.QueryProjection | ||||||
|  |  | ||||||
|  | data class GetSignatureCanListResponse( | ||||||
|  |     val totalCount: Int, | ||||||
|  |     val items: List<GetSignatureCanListItem> | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | data class GetSignatureCanListItem @QueryProjection constructor( | ||||||
|  |     val id: Long, | ||||||
|  |     val can: Int, | ||||||
|  |     val image: String, | ||||||
|  |     val nickname: String | ||||||
|  | ) | ||||||
| @@ -11,5 +11,8 @@ data class CreateLiveRoomRequest( | |||||||
|     val price: Int = 0, |     val price: Int = 0, | ||||||
|     val timezone: String, |     val timezone: String, | ||||||
|     val type: LiveRoomType = LiveRoomType.OPEN, |     val type: LiveRoomType = LiveRoomType.OPEN, | ||||||
|     val password: String? = null |     val password: String? = null, | ||||||
|  |     val menuPanId: Long = 0, | ||||||
|  |     val menuPan: String = "", | ||||||
|  |     val isActiveMenuPan: Boolean = false | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -5,5 +5,8 @@ data class EditLiveRoomInfoRequest( | |||||||
|     val notice: String?, |     val notice: String?, | ||||||
|     val numberOfPeople: Int?, |     val numberOfPeople: Int?, | ||||||
|     val beginDateTimeString: String?, |     val beginDateTimeString: String?, | ||||||
|     val timezone: String? |     val timezone: String?, | ||||||
|  |     val menuPanId: Long = 0, | ||||||
|  |     val menuPan: String = "", | ||||||
|  |     val isActiveMenuPan: Boolean? = null | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -38,8 +38,12 @@ import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfo | |||||||
| import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfoRedisRepository | import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfoRedisRepository | ||||||
| import kr.co.vividnext.sodalive.live.room.info.LiveRoomMember | import kr.co.vividnext.sodalive.live.room.info.LiveRoomMember | ||||||
| import kr.co.vividnext.sodalive.live.room.kickout.LiveRoomKickOutService | import kr.co.vividnext.sodalive.live.room.kickout.LiveRoomKickOutService | ||||||
|  | import kr.co.vividnext.sodalive.live.room.menu.CreateLiveMenuRequest | ||||||
|  | import kr.co.vividnext.sodalive.live.room.menu.LiveRoomMenuService | ||||||
|  | import kr.co.vividnext.sodalive.live.room.menu.UpdateLiveMenuRequest | ||||||
| import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisitService | import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisitService | ||||||
| import kr.co.vividnext.sodalive.live.roulette.NewRouletteRepository | import kr.co.vividnext.sodalive.live.roulette.NewRouletteRepository | ||||||
|  | import kr.co.vividnext.sodalive.live.signature.SignatureCanRepository | ||||||
| import kr.co.vividnext.sodalive.live.tag.LiveTagRepository | import kr.co.vividnext.sodalive.live.tag.LiveTagRepository | ||||||
| import kr.co.vividnext.sodalive.member.Gender | import kr.co.vividnext.sodalive.member.Gender | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
| @@ -65,12 +69,15 @@ import kotlin.concurrent.write | |||||||
| @Service | @Service | ||||||
| @Transactional(readOnly = true) | @Transactional(readOnly = true) | ||||||
| class LiveRoomService( | class LiveRoomService( | ||||||
|  |     private val menuService: LiveRoomMenuService, | ||||||
|  |  | ||||||
|     private val repository: LiveRoomRepository, |     private val repository: LiveRoomRepository, | ||||||
|     private val rouletteRepository: NewRouletteRepository, |     private val rouletteRepository: NewRouletteRepository, | ||||||
|     private val roomInfoRepository: LiveRoomInfoRedisRepository, |     private val roomInfoRepository: LiveRoomInfoRedisRepository, | ||||||
|     private val roomCancelRepository: LiveRoomCancelRepository, |     private val roomCancelRepository: LiveRoomCancelRepository, | ||||||
|     private val kickOutService: LiveRoomKickOutService, |     private val kickOutService: LiveRoomKickOutService, | ||||||
|     private val blockMemberRepository: BlockMemberRepository, |     private val blockMemberRepository: BlockMemberRepository, | ||||||
|  |     private val signatureCanRepository: SignatureCanRepository, | ||||||
|  |  | ||||||
|     private val applicationEventPublisher: ApplicationEventPublisher, |     private val applicationEventPublisher: ApplicationEventPublisher, | ||||||
|     private val useCanCalculateRepository: UseCanCalculateRepository, |     private val useCanCalculateRepository: UseCanCalculateRepository, | ||||||
| @@ -269,6 +276,27 @@ class LiveRoomService( | |||||||
|             room.bgImage = request.coverImageUrl |             room.bgImage = request.coverImageUrl | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (request.isActiveMenuPan) { | ||||||
|  |             if (request.menuPanId > 0) { | ||||||
|  |                 menuService.updateLiveMenu( | ||||||
|  |                     memberId = member.id!!, | ||||||
|  |                     request = UpdateLiveMenuRequest( | ||||||
|  |                         id = request.menuPanId, | ||||||
|  |                         menu = request.menuPan, | ||||||
|  |                         isActive = true | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             } else { | ||||||
|  |                 menuService.createLiveMenu( | ||||||
|  |                     memberId = member.id!!, | ||||||
|  |                     request = CreateLiveMenuRequest( | ||||||
|  |                         menu = request.menuPan, | ||||||
|  |                         isActive = true | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         applicationEventPublisher.publishEvent( |         applicationEventPublisher.publishEvent( | ||||||
|             FcmEvent( |             FcmEvent( | ||||||
|                 type = FcmEventType.CREATE_LIVE, |                 type = FcmEventType.CREATE_LIVE, | ||||||
| @@ -604,6 +632,28 @@ class LiveRoomService( | |||||||
|             throw SodaException("변경사항이 없습니다.") |             throw SodaException("변경사항이 없습니다.") | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (coverImage != null) { | ||||||
|  |             val metadata = ObjectMetadata() | ||||||
|  |             metadata.contentLength = coverImage.size | ||||||
|  |  | ||||||
|  |             // 커버 이미지 파일명 생성 | ||||||
|  |             val coverImageFileName = generateFileName(prefix = "${room.id}-cover") | ||||||
|  |  | ||||||
|  |             // 커버 이미지 업로드 | ||||||
|  |             val coverImagePath = s3Uploader.upload( | ||||||
|  |                 inputStream = coverImage.inputStream, | ||||||
|  |                 bucket = coverImageBucket, | ||||||
|  |                 filePath = "live_room_cover/${room.id}/$coverImageFileName", | ||||||
|  |                 metadata = metadata | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |             room.bgImage = coverImagePath | ||||||
|  |  | ||||||
|  |             if (room.channelName == null) { | ||||||
|  |                 room.coverImage = coverImagePath | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (requestString != null) { |         if (requestString != null) { | ||||||
|             val request = objectMapper.readValue(requestString, EditLiveRoomInfoRequest::class.java) |             val request = objectMapper.readValue(requestString, EditLiveRoomInfoRequest::class.java) | ||||||
|  |  | ||||||
| @@ -625,27 +675,30 @@ class LiveRoomService( | |||||||
|                     .withZoneSameInstant(ZoneId.of("UTC")) |                     .withZoneSameInstant(ZoneId.of("UTC")) | ||||||
|                     .toLocalDateTime() |                     .toLocalDateTime() | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (coverImage != null) { |             if (request.isActiveMenuPan != null) { | ||||||
|             val metadata = ObjectMetadata() |                 if (request.isActiveMenuPan) { | ||||||
|             metadata.contentLength = coverImage.size |                     if (request.menuPanId > 0) { | ||||||
|  |                         menuService.updateLiveMenu( | ||||||
|             // 커버 이미지 파일명 생성 |                             memberId = member.id!!, | ||||||
|             val coverImageFileName = generateFileName(prefix = "${room.id}-cover") |                             request = UpdateLiveMenuRequest( | ||||||
|  |                                 id = request.menuPanId, | ||||||
|             // 커버 이미지 업로드 |                                 menu = request.menuPan, | ||||||
|             val coverImagePath = s3Uploader.upload( |                                 isActive = true | ||||||
|                 inputStream = coverImage.inputStream, |                             ) | ||||||
|                 bucket = coverImageBucket, |                         ) | ||||||
|                 filePath = "live_room_cover/${room.id}/$coverImageFileName", |                     } else { | ||||||
|                 metadata = metadata |                         menuService.createLiveMenu( | ||||||
|             ) |                             memberId = member.id!!, | ||||||
|  |                             request = CreateLiveMenuRequest( | ||||||
|             room.bgImage = coverImagePath |                                 menu = request.menuPan, | ||||||
|  |                                 isActive = true | ||||||
|             if (room.channelName == null) { |                             ) | ||||||
|                 room.coverImage = coverImagePath |                         ) | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     menuService.deactivateAll(memberId = member.id!!) | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -703,6 +756,8 @@ class LiveRoomService( | |||||||
|             listOf() |             listOf() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         val menuPan = menuService.getLiveMenu(creatorId = room.member!!.id!!) | ||||||
|  |  | ||||||
|         return GetRoomInfoResponse( |         return GetRoomInfoResponse( | ||||||
|             roomId = roomId, |             roomId = roomId, | ||||||
|             title = room.title, |             title = room.title, | ||||||
| @@ -738,6 +793,7 @@ class LiveRoomService( | |||||||
|             listenerList = roomInfo.listenerList, |             listenerList = roomInfo.listenerList, | ||||||
|             managerList = roomInfo.managerList, |             managerList = roomInfo.managerList, | ||||||
|             donationRankingTop3UserIds = donationRankingTop3UserIds, |             donationRankingTop3UserIds = donationRankingTop3UserIds, | ||||||
|  |             menuPan = menuPan?.menu ?: "", | ||||||
|             isPrivateRoom = room.type == LiveRoomType.PRIVATE, |             isPrivateRoom = room.type == LiveRoomType.PRIVATE, | ||||||
|             password = room.password, |             password = room.password, | ||||||
|             isActiveRoulette = isActiveRoulette |             isActiveRoulette = isActiveRoulette | ||||||
| @@ -916,7 +972,7 @@ class LiveRoomService( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Transactional |     @Transactional | ||||||
|     fun donation(request: LiveRoomDonationRequest, member: Member) { |     fun donation(request: LiveRoomDonationRequest, member: Member): String? { | ||||||
|         val room = repository.findByIdOrNull(request.roomId) |         val room = repository.findByIdOrNull(request.roomId) | ||||||
|             ?: throw SodaException("해당하는 라이브가 없습니다.") |             ?: throw SodaException("해당하는 라이브가 없습니다.") | ||||||
|  |  | ||||||
| @@ -949,6 +1005,12 @@ class LiveRoomService( | |||||||
|                 roomInfoRepository.save(roomInfo) |                 roomInfoRepository.save(roomInfo) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         return signatureCanRepository.findImageByCreatorIdAndCan( | ||||||
|  |             creatorId = host.id!!, | ||||||
|  |             can = request.can, | ||||||
|  |             imageHost = cloudFrontHost | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Transactional |     @Transactional | ||||||
| @@ -1019,6 +1081,8 @@ class LiveRoomService( | |||||||
|                             rouletteRepository.save(it) |                             rouletteRepository.save(it) | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  |                     menuService.deactivateAll(memberId = member.id!!) | ||||||
|                 } else { |                 } else { | ||||||
|                     roomInfo.removeSpeaker(member) |                     roomInfo.removeSpeaker(member) | ||||||
|                     roomInfo.removeListener(member) |                     roomInfo.removeListener(member) | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ data class GetRoomInfoResponse( | |||||||
|     val listenerList: List<LiveRoomMember>, |     val listenerList: List<LiveRoomMember>, | ||||||
|     val managerList: List<LiveRoomMember>, |     val managerList: List<LiveRoomMember>, | ||||||
|     val donationRankingTop3UserIds: List<Long>, |     val donationRankingTop3UserIds: List<Long>, | ||||||
|  |     val menuPan: String, | ||||||
|     val isPrivateRoom: Boolean = false, |     val isPrivateRoom: Boolean = false, | ||||||
|     val password: String? = null, |     val password: String? = null, | ||||||
|     val isActiveRoulette: Boolean = false |     val isActiveRoulette: Boolean = false | ||||||
|   | |||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.room.menu | ||||||
|  |  | ||||||
|  | data class CreateLiveMenuRequest(val menu: String, val isActive: Boolean) | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.room.menu | ||||||
|  |  | ||||||
|  | data class GetMenuPresetResponse( | ||||||
|  |     val id: Long, | ||||||
|  |     val menu: String, | ||||||
|  |     val isActive: Boolean | ||||||
|  | ) | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.room.menu | ||||||
|  |  | ||||||
|  | import org.springframework.data.redis.core.RedisHash | ||||||
|  | import org.springframework.data.redis.core.index.Indexed | ||||||
|  | import javax.persistence.Id | ||||||
|  |  | ||||||
|  | @RedisHash("LiveRoomMenu") | ||||||
|  | data class LiveRoomMenu( | ||||||
|  |     @Id | ||||||
|  |     val id: Long, | ||||||
|  |     @Indexed | ||||||
|  |     val creatorId: Long, | ||||||
|  |     var isActive: Boolean, | ||||||
|  |     var menu: String | ||||||
|  | ) | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.room.menu | ||||||
|  |  | ||||||
|  | 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.GetMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
|  | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
|  | @RestController | ||||||
|  | @RequestMapping("/live/room/menu") | ||||||
|  | class LiveRoomMenuController(private val service: LiveRoomMenuService) { | ||||||
|  |     @GetMapping("/all") | ||||||
|  |     @PreAuthorize("hasRole('CREATOR')") | ||||||
|  |     fun getAllLiveMenu( | ||||||
|  |         @RequestParam creatorId: Long, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok(service.getAllLiveMenu(creatorId = creatorId, memberId = member.id!!)) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.room.menu | ||||||
|  |  | ||||||
|  | import org.springframework.data.repository.CrudRepository | ||||||
|  |  | ||||||
|  | interface LiveRoomMenuRepository : CrudRepository<LiveRoomMenu, Long> { | ||||||
|  |     fun findByCreatorId(creatorId: Long): List<LiveRoomMenu> | ||||||
|  | } | ||||||
| @@ -0,0 +1,105 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.room.menu | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | import kr.co.vividnext.sodalive.live.roulette.RedisIdGenerator | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class LiveRoomMenuService( | ||||||
|  |     private val idGenerator: RedisIdGenerator, | ||||||
|  |     private val repository: LiveRoomMenuRepository | ||||||
|  | ) { | ||||||
|  |     fun getAllLiveMenu(creatorId: Long, memberId: Long): List<GetMenuPresetResponse> { | ||||||
|  |         if (creatorId != memberId) throw SodaException("잘못된 요청입니다.") | ||||||
|  |  | ||||||
|  |         return repository.findByCreatorId(creatorId) | ||||||
|  |             .sortedBy { it.id } | ||||||
|  |             .asSequence() | ||||||
|  |             .map { GetMenuPresetResponse(id = it.id, menu = it.menu, isActive = it.isActive) } | ||||||
|  |             .toList() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun createLiveMenu(memberId: Long, request: CreateLiveMenuRequest): Boolean { | ||||||
|  |         liveMenuValidate(menu = request.menu) | ||||||
|  |  | ||||||
|  |         val menuList = repository.findByCreatorId(creatorId = memberId) | ||||||
|  |  | ||||||
|  |         if (menuList.size >= 3) { | ||||||
|  |             throw SodaException("메뉴판의 최대개수는 3개입니다.") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (request.isActive) { | ||||||
|  |             menuList.forEach { | ||||||
|  |                 it.isActive = false | ||||||
|  |                 repository.save(it) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val menu = LiveRoomMenu( | ||||||
|  |             id = idGenerator.generateId(SEQUENCE_NAME), | ||||||
|  |             creatorId = memberId, | ||||||
|  |             isActive = request.isActive, | ||||||
|  |             menu = request.menu | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         repository.save(menu) | ||||||
|  |         return request.isActive | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun updateLiveMenu(memberId: Long, request: UpdateLiveMenuRequest) { | ||||||
|  |         liveMenuValidate(menu = request.menu) | ||||||
|  |  | ||||||
|  |         val menuList = repository.findByCreatorId(creatorId = memberId) | ||||||
|  |         if (menuList.isEmpty()) { | ||||||
|  |             throw SodaException("잘못된 요청입니다.") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         menuList.forEach { | ||||||
|  |             if (it.id == request.id) { | ||||||
|  |                 it.menu = request.menu | ||||||
|  |                 it.isActive = request.isActive | ||||||
|  |                 repository.save(it) | ||||||
|  |             } else if (request.isActive) { | ||||||
|  |                 it.isActive = false | ||||||
|  |                 repository.save(it) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getLiveMenu(creatorId: Long): GetMenuPresetResponse? { | ||||||
|  |         val menuList = repository.findByCreatorId(creatorId = creatorId) | ||||||
|  |  | ||||||
|  |         var activeMenu: LiveRoomMenu? = null | ||||||
|  |         for (menu in menuList) { | ||||||
|  |             if (menu.isActive) { | ||||||
|  |                 activeMenu = menu | ||||||
|  |                 break | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (activeMenu == null || activeMenu.menu.isEmpty()) { | ||||||
|  |             return null | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return GetMenuPresetResponse(id = activeMenu.id, menu = activeMenu.menu, isActive = activeMenu.isActive) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun deactivateAll(memberId: Long) { | ||||||
|  |         val menuList = repository.findByCreatorId(creatorId = memberId) | ||||||
|  |  | ||||||
|  |         menuList.forEach { | ||||||
|  |             it.isActive = false | ||||||
|  |             repository.save(it) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun liveMenuValidate(menu: String) { | ||||||
|  |         if (menu.isBlank()) { | ||||||
|  |             throw SodaException("메뉴판은 빈칸일 수 없습니다.") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         const val SEQUENCE_NAME = "LiveRoomMenu:sequence" | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.room.menu | ||||||
|  |  | ||||||
|  | data class UpdateLiveMenuRequest(val id: Long, val menu: String, val isActive: Boolean) | ||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.signature | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.common.BaseEntity | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import javax.persistence.Entity | ||||||
|  | import javax.persistence.FetchType | ||||||
|  | import javax.persistence.JoinColumn | ||||||
|  | import javax.persistence.ManyToOne | ||||||
|  |  | ||||||
|  | @Entity | ||||||
|  | data class SignatureCan( | ||||||
|  |     val can: Int, | ||||||
|  |     var isActive: Boolean = true | ||||||
|  | ) : BaseEntity() { | ||||||
|  |     var image: String? = null | ||||||
|  |  | ||||||
|  |     @ManyToOne(fetch = FetchType.LAZY) | ||||||
|  |     @JoinColumn(name = "creator_id", nullable = false) | ||||||
|  |     var creator: Member? = null | ||||||
|  | } | ||||||
| @@ -0,0 +1,25 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.signature | ||||||
|  |  | ||||||
|  | import com.querydsl.jpa.impl.JPAQueryFactory | ||||||
|  | import kr.co.vividnext.sodalive.live.signature.QSignatureCan.signatureCan | ||||||
|  | import org.springframework.data.jpa.repository.JpaRepository | ||||||
|  |  | ||||||
|  | interface SignatureCanRepository : JpaRepository<SignatureCan, Long>, SignatureCanQueryRepository | ||||||
|  |  | ||||||
|  | interface SignatureCanQueryRepository { | ||||||
|  |     fun findImageByCreatorIdAndCan(creatorId: Long, can: Int, imageHost: String): String? | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SignatureCanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : SignatureCanQueryRepository { | ||||||
|  |     override fun findImageByCreatorIdAndCan(creatorId: Long, can: Int, imageHost: String): String? { | ||||||
|  |         return queryFactory | ||||||
|  |             .select(signatureCan.image.prepend("/").prepend(imageHost)) | ||||||
|  |             .from(signatureCan) | ||||||
|  |             .where( | ||||||
|  |                 signatureCan.creator.id.eq(creatorId) | ||||||
|  |                     .and(signatureCan.can.eq(can)) | ||||||
|  |                     .and(signatureCan.isActive.isTrue) | ||||||
|  |             ) | ||||||
|  |             .fetchFirst() | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user