관리자용 이벤트 배너 API
This commit is contained in:
		| @@ -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 | ||||
| ) | ||||
		Reference in New Issue
	
	Block a user