| @@ -3,6 +3,7 @@ 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.creator.admin.content.series.QSeries.series | ||||
| import kr.co.vividnext.sodalive.event.QEvent.event | ||||
| import kr.co.vividnext.sodalive.member.QMember.member | ||||
| import org.springframework.beans.factory.annotation.Value | ||||
| @@ -32,6 +33,8 @@ class AdminContentBannerQueryRepositoryImpl( | ||||
|                     audioContentBanner.event.thumbnailImage, | ||||
|                     audioContentBanner.creator.id, | ||||
|                     audioContentBanner.creator.nickname, | ||||
|                     audioContentBanner.series.id, | ||||
|                     audioContentBanner.series.title, | ||||
|                     audioContentBanner.link, | ||||
|                     audioContentBanner.isAdult | ||||
|                 ) | ||||
| @@ -39,6 +42,7 @@ class AdminContentBannerQueryRepositoryImpl( | ||||
|             .from(audioContentBanner) | ||||
|             .leftJoin(audioContentBanner.event, event) | ||||
|             .leftJoin(audioContentBanner.creator, member) | ||||
|             .leftJoin(audioContentBanner.series, series) | ||||
|             .where(audioContentBanner.isActive.isTrue) | ||||
|             .orderBy(audioContentBanner.orders.asc()) | ||||
|             .fetch() | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.banner | ||||
|  | ||||
| import com.fasterxml.jackson.databind.ObjectMapper | ||||
| import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository | ||||
| import kr.co.vividnext.sodalive.aws.s3.S3Uploader | ||||
| import kr.co.vividnext.sodalive.common.SodaException | ||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner | ||||
| @@ -19,6 +20,7 @@ class AdminContentBannerService( | ||||
|     private val s3Uploader: S3Uploader, | ||||
|     private val repository: AdminContentBannerRepository, | ||||
|     private val memberRepository: MemberRepository, | ||||
|     private val seriesRepository: AdminContentSeriesRepository, | ||||
|     private val eventRepository: EventRepository, | ||||
|     private val objectMapper: ObjectMapper, | ||||
|  | ||||
| @@ -32,6 +34,10 @@ class AdminContentBannerService( | ||||
|             throw SodaException("크리에이터를 선택하세요.") | ||||
|         } | ||||
|  | ||||
|         if (request.type == AudioContentBannerType.SERIES && request.seriesId == null) { | ||||
|             throw SodaException("시리즈를 선택하세요.") | ||||
|         } | ||||
|  | ||||
|         if (request.type == AudioContentBannerType.LINK && request.link == null) { | ||||
|             throw SodaException("링크 url을 입력하세요.") | ||||
|         } | ||||
| @@ -52,11 +58,18 @@ class AdminContentBannerService( | ||||
|             null | ||||
|         } | ||||
|  | ||||
|         val series = if (request.seriesId != null && request.seriesId > 0) { | ||||
|             seriesRepository.findByIdOrNull(request.seriesId) | ||||
|         } else { | ||||
|             null | ||||
|         } | ||||
|  | ||||
|         val audioContentBanner = AudioContentBanner(type = request.type) | ||||
|         audioContentBanner.link = request.link | ||||
|         audioContentBanner.isAdult = request.isAdult | ||||
|         audioContentBanner.event = event | ||||
|         audioContentBanner.creator = creator | ||||
|         audioContentBanner.series = series | ||||
|         repository.save(audioContentBanner) | ||||
|  | ||||
|         val fileName = generateFileName() | ||||
| @@ -96,8 +109,21 @@ class AdminContentBannerService( | ||||
|             audioContentBanner.creator = null | ||||
|             audioContentBanner.event = null | ||||
|             audioContentBanner.link = null | ||||
|             audioContentBanner.series = null | ||||
|  | ||||
|             if (request.type == AudioContentBannerType.CREATOR) { | ||||
|             when (request.type) { | ||||
|                 AudioContentBannerType.EVENT -> { | ||||
|                     if (request.eventId != null) { | ||||
|                         val event = eventRepository.findByIdOrNull(request.eventId) | ||||
|                             ?: throw SodaException("이벤트를 선택하세요.") | ||||
|  | ||||
|                         audioContentBanner.event = event | ||||
|                     } else { | ||||
|                         throw SodaException("이벤트를 선택하세요.") | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 AudioContentBannerType.CREATOR -> { | ||||
|                     if (request.creatorId != null) { | ||||
|                         val creator = memberRepository.findByIdOrNull(request.creatorId) | ||||
|                             ?: throw SodaException("크리에이터를 선택하세요.") | ||||
| @@ -106,20 +132,25 @@ class AdminContentBannerService( | ||||
|                     } else { | ||||
|                         throw SodaException("크리에이터를 선택하세요.") | ||||
|                     } | ||||
|             } else if (request.type == AudioContentBannerType.LINK) { | ||||
|                 } | ||||
|  | ||||
|                 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 | ||||
|                 AudioContentBannerType.SERIES -> { | ||||
|                     if (request.seriesId != null) { | ||||
|                         val series = seriesRepository.findByIdOrNull(request.seriesId) | ||||
|                             ?: throw SodaException("시리즈를 선택하세요.") | ||||
|  | ||||
|                         audioContentBanner.series = series | ||||
|                     } else { | ||||
|                     throw SodaException("이벤트를 선택하세요.") | ||||
|                         throw SodaException("시리즈를 선택하세요.") | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,7 @@ data class CreateContentBannerRequest( | ||||
|     val type: AudioContentBannerType, | ||||
|     val eventId: Long?, | ||||
|     val creatorId: Long?, | ||||
|     val seriesId: Long?, | ||||
|     val link: String?, | ||||
|     val isAdult: Boolean | ||||
| ) | ||||
|   | ||||
| @@ -11,6 +11,8 @@ data class GetAdminContentBannerResponse @QueryProjection constructor( | ||||
|     val eventThumbnailImage: String?, | ||||
|     val creatorId: Long?, | ||||
|     val creatorNickname: String?, | ||||
|     val seriesId: Long?, | ||||
|     val seriesTitle: String?, | ||||
|     val link: String?, | ||||
|     val isAdult: Boolean | ||||
| ) | ||||
|   | ||||
| @@ -7,6 +7,7 @@ data class UpdateContentBannerRequest( | ||||
|     val type: AudioContentBannerType?, | ||||
|     val eventId: Long?, | ||||
|     val creatorId: Long?, | ||||
|     val seriesId: Long?, | ||||
|     val link: String?, | ||||
|     val isAdult: Boolean?, | ||||
|     val isActive: Boolean? | ||||
|   | ||||
| @@ -5,6 +5,7 @@ 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 | ||||
| @@ -13,4 +14,9 @@ import org.springframework.web.bind.annotation.RestController | ||||
| class AdminContentSeriesController(private val service: AdminContentSeriesService) { | ||||
|     @GetMapping | ||||
|     fun getSeriesList(pageable: Pageable) = ApiResponse.ok(service.getSeriesList(pageable)) | ||||
|  | ||||
|     @GetMapping("/search") | ||||
|     fun searchSeriesList( | ||||
|         @RequestParam(value = "search_word") searchWord: String | ||||
|     ) = ApiResponse.ok(service.searchSeriesList(searchWord)) | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,8 @@ interface AdminContentSeriesQueryRepository { | ||||
|         offset: Long, | ||||
|         limit: Long | ||||
|     ): List<GetAdminSeriesListItem> | ||||
|  | ||||
|     fun searchSeriesList(searchWord: String): List<GetAdminSearchSeriesListItem> | ||||
| } | ||||
|  | ||||
| class AdminContentSeriesQueryRepositoryImpl( | ||||
| @@ -31,6 +33,7 @@ class AdminContentSeriesQueryRepositoryImpl( | ||||
|     override fun getSeriesTotalCount(): Int { | ||||
|         val where = series.isActive.isTrue | ||||
|             .and(series.member.isNotNull) | ||||
|             .and(series.member.isActive.isTrue) | ||||
|  | ||||
|         return queryFactory | ||||
|             .select(series.id) | ||||
| @@ -43,6 +46,7 @@ class AdminContentSeriesQueryRepositoryImpl( | ||||
|     override fun getSeriesList(offset: Long, limit: Long): List<GetAdminSeriesListItem> { | ||||
|         val where = series.isActive.isTrue | ||||
|             .and(series.member.isNotNull) | ||||
|             .and(series.member.isActive.isTrue) | ||||
|  | ||||
|         return queryFactory | ||||
|             .select( | ||||
| @@ -73,4 +77,24 @@ class AdminContentSeriesQueryRepositoryImpl( | ||||
|             .orderBy(series.member.id.asc(), series.orders.asc()) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     override fun searchSeriesList(searchWord: String): List<GetAdminSearchSeriesListItem> { | ||||
|         val where = series.isActive.isTrue | ||||
|             .and(series.title.contains(searchWord)) | ||||
|             .and(series.member.isNotNull) | ||||
|             .and(series.member.isActive.isTrue) | ||||
|  | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetAdminSearchSeriesListItem( | ||||
|                     series.id, | ||||
|                     series.title | ||||
|                 ) | ||||
|             ) | ||||
|             .from(series) | ||||
|             .innerJoin(series.member, member) | ||||
|             .where(where) | ||||
|             .orderBy(series.id.desc()) | ||||
|             .fetch() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -14,4 +14,8 @@ class AdminContentSeriesService(private val repository: AdminContentSeriesReposi | ||||
|  | ||||
|         return GetAdminSeriesListResponse(totalCount, items) | ||||
|     } | ||||
|  | ||||
|     fun searchSeriesList(searchWord: String): List<GetAdminSearchSeriesListItem> { | ||||
|         return repository.searchSeriesList(searchWord) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -18,3 +18,8 @@ data class GetAdminSeriesListItem @QueryProjection constructor( | ||||
|     val state: String, | ||||
|     val isAdult: Boolean | ||||
| ) | ||||
|  | ||||
| data class GetAdminSearchSeriesListItem @QueryProjection constructor( | ||||
|     val id: Long, | ||||
|     val title: String | ||||
| ) | ||||
|   | ||||
| @@ -0,0 +1,57 @@ | ||||
| package kr.co.vividnext.sodalive.admin.event.banner | ||||
|  | ||||
| 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("/admin/event/banner") | ||||
| @PreAuthorize("hasRole('ADMIN')") | ||||
| class AdminEventBannerController(private val service: AdminEventBannerService) { | ||||
|     @PostMapping | ||||
|     fun createEventBanner( | ||||
|         @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 = "isAdult", required = false) isAdult: Boolean? = null, | ||||
|         @RequestParam(value = "isPopup") isPopup: Boolean, | ||||
|         @RequestParam(value = "startDate") startDate: String, | ||||
|         @RequestParam(value = "endDate") endDate: String | ||||
|     ) = ApiResponse.ok( | ||||
|         service.save(thumbnail, detail, popup, link, title, isAdult, isPopup, startDate, endDate), | ||||
|         "등록되었습니다." | ||||
|     ) | ||||
|  | ||||
|     @PutMapping | ||||
|     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 = "isAdult", required = false) isAdult: Boolean? = null, | ||||
|         @RequestParam(value = "isPopup", required = false) isPopup: Boolean? = null, | ||||
|         @RequestParam(value = "startDate", required = false) startDate: String? = null, | ||||
|         @RequestParam(value = "endDate", required = false) endDate: String? = null | ||||
|     ) = ApiResponse.ok( | ||||
|         service.update(id, thumbnail, detail, popup, link, title, isAdult, isPopup, startDate, endDate), | ||||
|         "수정되었습니다." | ||||
|     ) | ||||
|  | ||||
|     @DeleteMapping("/{id}") | ||||
|     fun deleteEvent(@PathVariable id: Long) = ApiResponse.ok(service.delete(id), "삭제되었습니다.") | ||||
|  | ||||
|     @GetMapping | ||||
|     fun getEventList() = ApiResponse.ok(service.getEventList()) | ||||
| } | ||||
| @@ -0,0 +1,61 @@ | ||||
| package kr.co.vividnext.sodalive.admin.event.banner | ||||
|  | ||||
| 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.event.Event | ||||
| import kr.co.vividnext.sodalive.event.QEvent.event | ||||
| import org.springframework.data.jpa.repository.JpaRepository | ||||
| import java.time.LocalDateTime | ||||
|  | ||||
| interface AdminEventBannerRepository : JpaRepository<Event, Long>, AdminEventBannerQueryRepository | ||||
|  | ||||
| interface AdminEventBannerQueryRepository { | ||||
|     fun getEventList(): List<GetAdminEventResponse> | ||||
| } | ||||
|  | ||||
| class AdminEventBannerQueryRepositoryImpl( | ||||
|     private val queryFactory: JPAQueryFactory | ||||
| ) : AdminEventBannerQueryRepository { | ||||
|     override fun getEventList(): List<GetAdminEventResponse> { | ||||
|         val now = LocalDateTime.now() | ||||
|         val where = event.isActive.isTrue | ||||
|             .and(event.startDate.loe(now)) | ||||
|             .and(event.endDate.goe(now)) | ||||
|  | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetAdminEventResponse( | ||||
|                     event.id, | ||||
|                     event.title, | ||||
|                     event.thumbnailImage, | ||||
|                     event.detailImage, | ||||
|                     event.popupImage, | ||||
|                     getFormattedDate(event.startDate), | ||||
|                     getFormattedDate(event.endDate), | ||||
|                     event.link, | ||||
|                     event.isAdult, | ||||
|                     event.isPopup | ||||
|                 ) | ||||
|             ) | ||||
|             .from(event) | ||||
|             .where(where) | ||||
|             .orderBy(event.id.desc()) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     private fun getFormattedDate(dateTimePath: DateTimePath<LocalDateTime>): StringTemplate { | ||||
|         return Expressions.stringTemplate( | ||||
|             "DATE_FORMAT({0}, {1})", | ||||
|             Expressions.dateTimeTemplate( | ||||
|                 LocalDateTime::class.java, | ||||
|                 "CONVERT_TZ({0},{1},{2})", | ||||
|                 dateTimePath, | ||||
|                 "UTC", | ||||
|                 "Asia/Seoul" | ||||
|             ), | ||||
|             "%Y-%m-%d" | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,222 @@ | ||||
| package kr.co.vividnext.sodalive.admin.event.banner | ||||
|  | ||||
| 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.event.Event | ||||
| 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 | ||||
| import java.time.LocalDate | ||||
| import java.time.ZoneId | ||||
| import java.time.format.DateTimeFormatter | ||||
|  | ||||
| @Service | ||||
| class AdminEventBannerService( | ||||
|     private val repository: AdminEventBannerRepository, | ||||
|     private val s3Uploader: S3Uploader, | ||||
|  | ||||
|     @Value("\${cloud.aws.s3.bucket}") | ||||
|     private val bucket: String, | ||||
|     @Value("\${cloud.aws.cloud-front.host}") | ||||
|     private val cloudFrontHost: String | ||||
| ) { | ||||
|     @Transactional | ||||
|     fun save( | ||||
|         thumbnail: MultipartFile, | ||||
|         detail: MultipartFile? = null, | ||||
|         popup: MultipartFile? = null, | ||||
|         link: String? = null, | ||||
|         title: String? = null, | ||||
|         isAdult: Boolean? = null, | ||||
|         isPopup: Boolean, | ||||
|         startDateString: String, | ||||
|         endDateString: String | ||||
|     ): Long { | ||||
|         if (detail == null && link.isNullOrBlank()) throw SodaException("상세이미지 혹은 링크를 등록하세요") | ||||
|  | ||||
|         val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") | ||||
|         val startDate = LocalDate.parse(startDateString, dateTimeFormatter).atTime(0, 0) | ||||
|             .atZone(ZoneId.of("Asia/Seoul")) | ||||
|             .withZoneSameInstant(ZoneId.of("UTC")) | ||||
|             .toLocalDateTime() | ||||
|  | ||||
|         val endDate = LocalDate.parse(endDateString, dateTimeFormatter).atTime(23, 59, 59) | ||||
|             .atZone(ZoneId.of("Asia/Seoul")) | ||||
|             .withZoneSameInstant(ZoneId.of("UTC")) | ||||
|             .toLocalDateTime() | ||||
|  | ||||
|         val event = repository.save( | ||||
|             Event( | ||||
|                 thumbnailImage = "", | ||||
|                 detailImage = null, | ||||
|                 popupImage = null, | ||||
|                 link = link, | ||||
|                 title = title, | ||||
|                 isAdult = isAdult, | ||||
|                 isPopup = isPopup, | ||||
|                 startDate = startDate, | ||||
|                 endDate = endDate | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|         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, | ||||
|         isAdult: Boolean? = null, | ||||
|         isPopup: Boolean? = null, | ||||
|         startDateString: String? = null, | ||||
|         endDateString: String? = null | ||||
|     ) { | ||||
|         if (id <= 0) 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 | ||||
|         } | ||||
|  | ||||
|         if (isAdult != event.isAdult) { | ||||
|             event.isAdult = isAdult | ||||
|         } | ||||
|  | ||||
|         val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") | ||||
|         if (startDateString != null) { | ||||
|             event.startDate = LocalDate.parse(startDateString, dateTimeFormatter).atTime(0, 0) | ||||
|                 .atZone(ZoneId.of("Asia/Seoul")) | ||||
|                 .withZoneSameInstant(ZoneId.of("UTC")) | ||||
|                 .toLocalDateTime() | ||||
|         } | ||||
|  | ||||
|         if (endDateString != null) { | ||||
|             event.endDate = LocalDate.parse(endDateString, dateTimeFormatter).atTime(23, 59, 59) | ||||
|                 .atZone(ZoneId.of("Asia/Seoul")) | ||||
|                 .withZoneSameInstant(ZoneId.of("UTC")) | ||||
|                 .toLocalDateTime() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
|     fun delete(id: Long) { | ||||
|         if (id <= 0) throw SodaException("잘못된 요청입니다.") | ||||
|         val event = repository.findByIdOrNull(id) | ||||
|             ?: throw SodaException("잘못된 요청입니다.") | ||||
|  | ||||
|         event.isActive = false | ||||
|     } | ||||
|  | ||||
|     fun getEventList(): List<GetAdminEventResponse> { | ||||
|         return repository.getEventList() | ||||
|             .asSequence() | ||||
|             .map { | ||||
|                 if (!it.thumbnailImageUrl.startsWith("https://")) { | ||||
|                     it.thumbnailImageUrl = "$cloudFrontHost/${it.thumbnailImageUrl}" | ||||
|                 } | ||||
|  | ||||
|                 if (it.detailImageUrl != null && !it.detailImageUrl!!.startsWith("https://")) { | ||||
|                     it.detailImageUrl = "$cloudFrontHost/${it.detailImageUrl}" | ||||
|                 } | ||||
|  | ||||
|                 if (it.popupImageUrl != null && !it.popupImageUrl!!.startsWith("https://")) { | ||||
|                     it.popupImageUrl = "$cloudFrontHost/${it.popupImageUrl}" | ||||
|                 } | ||||
|  | ||||
|                 it | ||||
|             } | ||||
|             .toList() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,16 @@ | ||||
| package kr.co.vividnext.sodalive.admin.event.banner | ||||
|  | ||||
| import com.querydsl.core.annotations.QueryProjection | ||||
|  | ||||
| data class GetAdminEventResponse @QueryProjection constructor( | ||||
|     val id: Long, | ||||
|     val title: String? = null, | ||||
|     var thumbnailImageUrl: String, | ||||
|     var detailImageUrl: String? = null, | ||||
|     var popupImageUrl: String? = null, | ||||
|     var startDate: String, | ||||
|     var endDate: String, | ||||
|     val link: String? = null, | ||||
|     val isAdult: Boolean? = null, | ||||
|     val isPopup: Boolean | ||||
| ) | ||||
| @@ -1,4 +1,4 @@ | ||||
| package kr.co.vividnext.sodalive.admin.event | ||||
| package kr.co.vividnext.sodalive.admin.event.charge | ||||
| 
 | ||||
| import kr.co.vividnext.sodalive.common.ApiResponse | ||||
| import org.springframework.security.access.prepost.PreAuthorize | ||||
| @@ -11,22 +11,20 @@ import org.springframework.web.bind.annotation.RestController | ||||
| 
 | ||||
| @RestController | ||||
| @RequestMapping("/event/charge") | ||||
| @PreAuthorize("hasRole('ADMIN')") | ||||
| 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()) | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| package kr.co.vividnext.sodalive.admin.event | ||||
| package kr.co.vividnext.sodalive.admin.event.charge | ||||
| 
 | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.admin.event.QChargeEvent.chargeEvent | ||||
| import kr.co.vividnext.sodalive.admin.event.charge.QChargeEvent.chargeEvent | ||||
| import org.springframework.data.jpa.repository.JpaRepository | ||||
| import org.springframework.stereotype.Repository | ||||
| 
 | ||||
| @@ -1,4 +1,4 @@ | ||||
| package kr.co.vividnext.sodalive.admin.event | ||||
| package kr.co.vividnext.sodalive.admin.event.charge | ||||
| 
 | ||||
| import kr.co.vividnext.sodalive.common.SodaException | ||||
| import org.springframework.data.repository.findByIdOrNull | ||||
| @@ -1,4 +1,4 @@ | ||||
| package kr.co.vividnext.sodalive.admin.event | ||||
| package kr.co.vividnext.sodalive.admin.event.charge | ||||
| 
 | ||||
| import kr.co.vividnext.sodalive.common.BaseEntity | ||||
| import java.time.LocalDateTime | ||||
| @@ -1,4 +1,4 @@ | ||||
| package kr.co.vividnext.sodalive.admin.event | ||||
| package kr.co.vividnext.sodalive.admin.event.charge | ||||
| 
 | ||||
| data class CreateChargeEventRequest( | ||||
|     val title: String, | ||||
| @@ -1,4 +1,4 @@ | ||||
| package kr.co.vividnext.sodalive.admin.event | ||||
| package kr.co.vividnext.sodalive.admin.event.charge | ||||
| 
 | ||||
| data class GetChargeEventListResponse( | ||||
|     val id: Long, | ||||
| @@ -1,4 +1,4 @@ | ||||
| package kr.co.vividnext.sodalive.admin.event | ||||
| package kr.co.vividnext.sodalive.admin.event.charge | ||||
| 
 | ||||
| data class ModifyChargeEventRequest( | ||||
|     val id: Long, | ||||
| @@ -1,8 +1,8 @@ | ||||
| package kr.co.vividnext.sodalive.can.charge.event | ||||
|  | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.admin.event.ChargeEvent | ||||
| import kr.co.vividnext.sodalive.admin.event.QChargeEvent.chargeEvent | ||||
| import kr.co.vividnext.sodalive.admin.event.charge.ChargeEvent | ||||
| import kr.co.vividnext.sodalive.admin.event.charge.QChargeEvent.chargeEvent | ||||
| 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 | ||||
|   | ||||
| @@ -14,8 +14,6 @@ import org.springframework.cache.annotation.Cacheable | ||||
| import org.springframework.data.domain.Pageable | ||||
| import org.springframework.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | ||||
| import java.time.ZoneId | ||||
| import java.time.format.DateTimeFormatter | ||||
|  | ||||
| @Service | ||||
| class AudioContentMainService( | ||||
| @@ -93,13 +91,14 @@ class AudioContentMainService( | ||||
|     } | ||||
|  | ||||
|     @Transactional(readOnly = true) | ||||
|     @Cacheable(cacheNames = ["default"], key = "'contentMainBannerList:' + #memberId + ':' + #isAdult") | ||||
|     fun getAudioContentMainBannerList(memberId: Long, isAdult: Boolean) = | ||||
|         repository.getAudioContentMainBannerList(isAdult = isAdult) | ||||
|             .asSequence() | ||||
|             .filter { | ||||
|                 if (it.type == AudioContentBannerType.CREATOR && it.creator != null) { | ||||
|                     !blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = it.creator!!.id!!) | ||||
|                 } else if (it.type == AudioContentBannerType.SERIES && it.series != null) { | ||||
|                     !blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = it.series!!.member!!.id!!) | ||||
|                 } else { | ||||
|                     true | ||||
|                 } | ||||
| @@ -109,17 +108,6 @@ class AudioContentMainService( | ||||
|                     type = it.type, | ||||
|                     thumbnailImageUrl = "$imageHost/${it.thumbnailImage}", | ||||
|                     eventItem = if (it.type == AudioContentBannerType.EVENT && it.event != null) { | ||||
|                         val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") | ||||
|                         val startDate = it.event!!.startDate | ||||
|                             .atZone(ZoneId.of("UTC")) | ||||
|                             .withZoneSameInstant(ZoneId.of("Asia/Seoul")) | ||||
|                             .format(dateTimeFormatter) | ||||
|  | ||||
|                         val endDate = it.event!!.endDate | ||||
|                             .atZone(ZoneId.of("UTC")) | ||||
|                             .withZoneSameInstant(ZoneId.of("Asia/Seoul")) | ||||
|                             .format(dateTimeFormatter) | ||||
|  | ||||
|                         EventItem( | ||||
|                             id = it.event!!.id!!, | ||||
|                             thumbnailImageUrl = if (!it.event!!.thumbnailImage.startsWith("https://")) { | ||||
| @@ -136,11 +124,7 @@ class AudioContentMainService( | ||||
|                                 it.event!!.detailImage | ||||
|                             }, | ||||
|                             popupImageUrl = null, | ||||
|                             startDate = startDate, | ||||
|                             endDate = endDate, | ||||
|                             link = it.event!!.link, | ||||
|                             title = it.event!!.title, | ||||
|                             isPopup = false | ||||
|                             link = it.event!!.link | ||||
|                         ) | ||||
|                     } else { | ||||
|                         null | ||||
| @@ -150,6 +134,11 @@ class AudioContentMainService( | ||||
|                     } else { | ||||
|                         null | ||||
|                     }, | ||||
|                     seriesId = if (it.type == AudioContentBannerType.SERIES && it.series != null) { | ||||
|                         it.series!!.id | ||||
|                     } else { | ||||
|                         null | ||||
|                     }, | ||||
|                     link = it.link | ||||
|                 ) | ||||
|             } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| package kr.co.vividnext.sodalive.content.main.banner | ||||
|  | ||||
| import kr.co.vividnext.sodalive.common.BaseEntity | ||||
| import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab | ||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.Series | ||||
| import kr.co.vividnext.sodalive.event.Event | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| import javax.persistence.Column | ||||
| @@ -34,10 +36,18 @@ data class AudioContentBanner( | ||||
|     @JoinColumn(name = "creator_id", nullable = true) | ||||
|     var creator: Member? = null | ||||
|  | ||||
|     @OneToOne(fetch = FetchType.EAGER) | ||||
|     @JoinColumn(name = "series_id", nullable = true) | ||||
|     var series: Series? = null | ||||
|  | ||||
|     @OneToOne(fetch = FetchType.EAGER) | ||||
|     @JoinColumn(name = "tab_id", nullable = true) | ||||
|     var tab: AudioContentMainTab? = null | ||||
|  | ||||
|     @Column(nullable = true) | ||||
|     var link: String? = null | ||||
| } | ||||
|  | ||||
| enum class AudioContentBannerType { | ||||
|     EVENT, CREATOR, LINK | ||||
|     EVENT, CREATOR, LINK, SERIES | ||||
| } | ||||
|   | ||||
| @@ -8,5 +8,6 @@ data class GetAudioContentBannerResponse( | ||||
|     @JsonProperty("thumbnailImageUrl") val thumbnailImageUrl: String, | ||||
|     @JsonProperty("eventItem") val eventItem: EventItem?, | ||||
|     @JsonProperty("creatorId") val creatorId: Long?, | ||||
|     @JsonProperty("seriesId") val seriesId: Long?, | ||||
|     @JsonProperty("link") val link: String? | ||||
| ) | ||||
|   | ||||
| @@ -0,0 +1,10 @@ | ||||
| package kr.co.vividnext.sodalive.content.main.tab | ||||
|  | ||||
| import kr.co.vividnext.sodalive.common.BaseEntity | ||||
| import javax.persistence.Entity | ||||
|  | ||||
| @Entity | ||||
| data class AudioContentMainTab( | ||||
|     val title: String, | ||||
|     val isActive: Boolean | ||||
| ) : BaseEntity() | ||||
| @@ -1,8 +1,5 @@ | ||||
| package kr.co.vividnext.sodalive.event | ||||
|  | ||||
| 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.event.QEvent.event | ||||
| import org.springframework.data.jpa.repository.JpaRepository | ||||
| @@ -43,15 +40,10 @@ class EventQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Even | ||||
|             .select( | ||||
|                 QEventItem( | ||||
|                     event.id, | ||||
|                     event.title, | ||||
|                     event.thumbnailImage, | ||||
|                     event.detailImage, | ||||
|                     event.popupImage, | ||||
|                     getFormattedDate(event.startDate), | ||||
|                     getFormattedDate(event.endDate), | ||||
|                     event.link, | ||||
|                     event.isAdult, | ||||
|                     event.isPopup | ||||
|                     event.link | ||||
|                 ) | ||||
|             ) | ||||
|             .from(event) | ||||
| @@ -84,15 +76,10 @@ class EventQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Even | ||||
|             .select( | ||||
|                 QEventItem( | ||||
|                     event.id, | ||||
|                     event.title, | ||||
|                     event.thumbnailImage, | ||||
|                     event.detailImage, | ||||
|                     event.popupImage, | ||||
|                     getFormattedDate(event.startDate), | ||||
|                     getFormattedDate(event.endDate), | ||||
|                     event.link, | ||||
|                     event.isAdult, | ||||
|                     event.isPopup | ||||
|                     event.link | ||||
|                 ) | ||||
|             ) | ||||
|             .from(event) | ||||
| @@ -100,18 +87,4 @@ class EventQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Even | ||||
|             .orderBy(event.id.desc()) | ||||
|             .fetchFirst() | ||||
|     } | ||||
|  | ||||
|     private fun getFormattedDate(dateTimePath: DateTimePath<LocalDateTime>): StringTemplate { | ||||
|         return Expressions.stringTemplate( | ||||
|             "DATE_FORMAT({0}, {1})", | ||||
|             Expressions.dateTimeTemplate( | ||||
|                 LocalDateTime::class.java, | ||||
|                 "CONVERT_TZ({0},{1},{2})", | ||||
|                 dateTimePath, | ||||
|                 "UTC", | ||||
|                 "Asia/Seoul" | ||||
|             ), | ||||
|             "%Y-%m-%d" | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -12,13 +12,8 @@ data class GetEventResponse( | ||||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||||
| data class EventItem @QueryProjection constructor( | ||||
|     @JsonProperty("id") val id: Long, | ||||
|     @JsonProperty("title") val title: String? = null, | ||||
|     @JsonProperty("thumbnailImageUrl") var thumbnailImageUrl: String, | ||||
|     @JsonProperty("detailImageUrl") var detailImageUrl: String? = null, | ||||
|     @JsonProperty("popupImageUrl") var popupImageUrl: String? = null, | ||||
|     @JsonProperty("startDate") var startDate: String, | ||||
|     @JsonProperty("endDate") var endDate: String, | ||||
|     @JsonProperty("link") val link: String? = null, | ||||
|     @JsonProperty("isAdult") val isAdult: Boolean? = null, | ||||
|     @JsonProperty("isPopup") val isPopup: Boolean | ||||
|     @JsonProperty("link") val link: String? = null | ||||
| ) | ||||
|   | ||||
| @@ -2,10 +2,8 @@ package kr.co.vividnext.sodalive.member.notification | ||||
|  | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| import org.springframework.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | ||||
|  | ||||
| @Service | ||||
| @Transactional(readOnly = true) | ||||
| class MemberNotificationService(private val repository: MemberNotificationRepository) { | ||||
|     fun updateNotification( | ||||
|         live: Boolean? = null, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user