From c3cb8fe93bf5c959fb9c5892cae5062679c3eaa3 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Aug 2023 00:06:09 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=ED=81=AC=EB=A6=AC=EC=97=90=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EA=B4=80=EB=A6=AC=EC=9E=90=20-=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EB=A6=AC=EC=8A=A4=ED=8A=B8,=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/configs/SecurityConfig.kt | 1 + .../vividnext/sodalive/configs/WebConfig.kt | 2 + .../content/CreatorAdminContentController.kt | 51 +++++++ .../content/CreatorAdminContentRepository.kt | 126 ++++++++++++++++++ .../content/CreatorAdminContentService.kt | 121 +++++++++++++++++ .../GetCreatorAdminContentListResponse.kt | 24 ++++ .../member/CreatorAdminMemberController.kt | 31 +++++ .../member/CreatorAdminMemberService.kt | 93 +++++++++++++ .../vividnext/sodalive/menu/MenuController.kt | 2 +- 9 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/GetCreatorAdminContentListResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberService.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt index 986869f..b597a5c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt @@ -68,6 +68,7 @@ class SecurityConfig( .antMatchers("/member/check/nickname").permitAll() .antMatchers("/member/signup").permitAll() .antMatchers("/member/login").permitAll() + .antMatchers("/creator-admin/member/login").permitAll() .antMatchers("/member/forgot-password").permitAll() .antMatchers("/stplat/terms_of_service").permitAll() .antMatchers("/stplat/privacy_policy").permitAll() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/WebConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/WebConfig.kt index ffbb936..6c38c9c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/configs/WebConfig.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/WebConfig.kt @@ -10,6 +10,8 @@ class WebConfig : WebMvcConfigurer { registry.addMapping("/**") .allowedOrigins( "http://localhost:8888", + "https://creator.sodalive.net", + "https://test-creator.sodalive.net", "https://test-admin.sodalive.net", "https://admin.sodalive.net" ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentController.kt new file mode 100644 index 0000000..47b2491 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentController.kt @@ -0,0 +1,51 @@ +package kr.co.vividnext.sodalive.creator_admin.content + +import kr.co.vividnext.sodalive.admin.content.UpdateAdminContentRequest +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import org.springframework.data.domain.Pageable +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.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('CREATOR')") +@RequestMapping("/creator-admin/audio-content") +class CreatorAdminContentController(private val service: CreatorAdminContentService) { + @GetMapping("/list") + fun getAudioContentList( + pageable: Pageable, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.getAudioContentList(pageable, member)) + } + + @GetMapping("/search") + fun searchAudioContent( + @RequestParam(value = "search_word") searchWord: String, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, + pageable: Pageable + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.searchAudioContent(searchWord, member, pageable)) + } + + @PutMapping + fun modifyAudioContent( + @RequestBody request: UpdateAdminContentRequest, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.updateAudioContent(request, member)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentRepository.kt new file mode 100644 index 0000000..e93087a --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentRepository.kt @@ -0,0 +1,126 @@ +package kr.co.vividnext.sodalive.creator_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.theme.QAudioContentTheme.audioContentTheme +import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDateTime + +interface CreatorAdminContentRepository : JpaRepository, CreatorAdminAudioContentQueryRepository + +interface CreatorAdminAudioContentQueryRepository { + fun getAudioContentTotalCount(memberId: Long, searchWord: String = ""): Int + fun getAudioContentList( + memberId: Long, + offset: Long, + limit: Long, + searchWord: String = "" + ): List + + fun getHashTagList(audioContentId: Long): List +} + +class CreatorAdminAudioContentQueryRepositoryImpl( + private val queryFactory: JPAQueryFactory +) : CreatorAdminAudioContentQueryRepository { + override fun getAudioContentTotalCount(memberId: Long, searchWord: String): Int { + var where = audioContent.duration.isNotNull + .and(audioContent.member.isNotNull) + .and(audioContent.isActive.isTrue) + .and(audioContent.member.id.eq(memberId)) + + 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( + memberId: Long, + offset: Long, + limit: Long, + searchWord: String + ): List { + var where = audioContent.duration.isNotNull + .and(audioContent.member.isNotNull) + .and(audioContent.isActive.isTrue) + .and(audioContent.member.id.eq(memberId)) + + if (searchWord.trim().length > 1) { + where = where.and( + audioContent.title.contains(searchWord) + .or(audioContent.member.nickname.contains(searchWord)) + ) + } + + return queryFactory + .select( + QGetCreatorAdminContentListItem( + audioContent.id, + audioContent.title, + audioContent.detail, + audioContent.coverImage, + audioContent.member!!.nickname, + audioContentTheme.theme, + audioContent.price, + audioContent.isAdult, + audioContent.duration, + audioContent.content, + formattedDateExpression(audioContent.createdAt) + ) + ) + .from(audioContent) + .innerJoin(audioContent.theme, audioContentTheme) + .where(where) + .offset(offset) + .limit(limit) + .orderBy(audioContent.id.desc()) + .fetch() + } + + override fun getHashTagList(audioContentId: Long): List { + 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, + 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 + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentService.kt new file mode 100644 index 0000000..faf23c0 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentService.kt @@ -0,0 +1,121 @@ +package kr.co.vividnext.sodalive.creator_admin.content + +import kr.co.vividnext.sodalive.admin.content.UpdateAdminContentRequest +import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +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 CreatorAdminContentService( + private val repository: CreatorAdminContentRepository, + private val audioContentCloudFront: AudioContentCloudFront, + + @Value("\${cloud.aws.cloud-front.host}") + private val coverImageHost: String +) { + fun getAudioContentList(pageable: Pageable, member: Member): GetCreatorAdminContentListResponse { + val totalCount = repository.getAudioContentTotalCount(memberId = member.id!!) + val audioContentAndThemeList = repository.getAudioContentList( + memberId = member.id!!, + 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 GetCreatorAdminContentListResponse(totalCount, audioContentList) + } + + fun searchAudioContent(searchWord: String, member: Member, pageable: Pageable): GetCreatorAdminContentListResponse { + if (searchWord.length < 2) throw SodaException("2글자 이상 입력하세요.") + val totalCount = repository.getAudioContentTotalCount( + memberId = member.id!!, + searchWord + ) + val audioContentAndThemeList = repository.getAudioContentList( + memberId = member.id!!, + 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 GetCreatorAdminContentListResponse(totalCount, audioContentList) + } + + @Transactional + fun updateAudioContent(request: UpdateAdminContentRequest, member: Member) { + 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 + } + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/GetCreatorAdminContentListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/GetCreatorAdminContentListResponse.kt new file mode 100644 index 0000000..efc86d6 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/GetCreatorAdminContentListResponse.kt @@ -0,0 +1,24 @@ +package kr.co.vividnext.sodalive.creator_admin.content + +import com.querydsl.core.annotations.QueryProjection + +data class GetCreatorAdminContentListResponse( + val totalCount: Int, + val items: List +) + +data class GetCreatorAdminContentListItem @QueryProjection constructor( + val audioContentId: Long, + val title: String, + val detail: String, + 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 = "" +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberController.kt new file mode 100644 index 0000000..f64773c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberController.kt @@ -0,0 +1,31 @@ +package kr.co.vividnext.sodalive.creator_admin.member + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.member.login.LoginRequest +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestHeader +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/creator-admin/member") +class CreatorAdminMemberController(private val service: CreatorAdminMemberService) { + @PostMapping("/login") + fun login(@RequestBody loginRequest: LoginRequest) = service.login(loginRequest) + + @PostMapping("/logout") + @PreAuthorize("hasRole('CREATOR')") + fun logout( + @RequestHeader("Authorization") token: String, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.logout(token.removePrefix("Bearer "), member.id!!)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberService.kt new file mode 100644 index 0000000..9ecff6e --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberService.kt @@ -0,0 +1,93 @@ +package kr.co.vividnext.sodalive.creator_admin.member + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.jwt.TokenProvider +import kr.co.vividnext.sodalive.member.MemberRepository +import kr.co.vividnext.sodalive.member.MemberRole +import kr.co.vividnext.sodalive.member.login.LoginRequest +import kr.co.vividnext.sodalive.member.login.LoginResponse +import kr.co.vividnext.sodalive.member.token.MemberTokenRepository +import org.springframework.beans.factory.annotation.Value +import org.springframework.data.repository.findByIdOrNull +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.write + +@Service +class CreatorAdminMemberService( + private val repository: MemberRepository, + private val tokenRepository: MemberTokenRepository, + private val tokenProvider: TokenProvider, + private val authenticationManagerBuilder: AuthenticationManagerBuilder, + + @Value("\${cloud.aws.cloud-front.host}") + private val cloudFrontHost: String +) { + + private val tokenLocks: MutableMap = mutableMapOf() + + fun login(request: LoginRequest): ApiResponse { + return ApiResponse.ok( + message = "로그인 되었습니다.", + data = login(request.email, request.password) + ) + } + + @Transactional + fun logout(token: String, memberId: Long) { + val member = repository.findByIdOrNull(memberId) + ?: throw SodaException("로그인 정보를 확인해주세요.") + + member.pushToken = null + + val lock = getOrCreateLock(memberId = memberId) + lock.write { + val memberToken = tokenRepository.findByIdOrNull(memberId) + ?: throw SodaException("로그인 정보를 확인해주세요.") + + memberToken.tokenSet.remove(token) + tokenRepository.save(memberToken) + } + } + + private fun login(email: String, password: String): LoginResponse { + val member = repository.findByEmail(email = email) ?: throw SodaException("로그인 정보를 확인해주세요.") + if (!member.isActive) { + throw SodaException("탈퇴한 계정입니다.\n고객센터로 문의해 주시기 바랍니다.") + } + + if (member.role != MemberRole.CREATOR) { + throw SodaException("로그인 정보를 확인해주세요.") + } + + val authenticationToken = UsernamePasswordAuthenticationToken(email, password) + val authentication = authenticationManagerBuilder.`object`.authenticate(authenticationToken) + SecurityContextHolder.getContext().authentication = authentication + + val jwt = tokenProvider.createToken( + authentication = authentication, + memberId = member.id!! + ) + + return LoginResponse( + userId = member.id!!, + token = jwt, + nickname = member.nickname, + email = member.email, + profileImage = if (member.profileImage != null) { + "$cloudFrontHost/${member.profileImage}" + } else { + "$cloudFrontHost/profile/default-profile.png" + } + ) + } + + private fun getOrCreateLock(memberId: Long): ReentrantReadWriteLock { + return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() } + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/menu/MenuController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/menu/MenuController.kt index 5146119..d9b8e7b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/menu/MenuController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/menu/MenuController.kt @@ -12,6 +12,6 @@ import org.springframework.web.bind.annotation.RestController @RequestMapping("/menu") class MenuController(private val service: MenuService) { @GetMapping - @PreAuthorize("hasAnyRole('AGENT', 'ADMIN')") + @PreAuthorize("hasAnyRole('AGENT', 'ADMIN', 'CREATOR')") fun getMenus(@AuthenticationPrincipal user: User) = ApiResponse.ok(service.getMenus(user)) } -- 2.40.1 From 307c09a4c159a27103b7ea29c44e2c54736f788c Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Aug 2023 14:57:25 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=ED=81=AC=EB=A6=AC=EC=97=90=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EA=B4=80=EB=A6=AC=EC=9E=90=20-=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin}/content/CreatorAdminContentController.kt | 2 +- .../admin}/content/CreatorAdminContentRepository.kt | 2 +- .../admin}/content/CreatorAdminContentService.kt | 2 +- .../admin}/content/GetCreatorAdminContentListResponse.kt | 2 +- .../admin}/member/CreatorAdminMemberController.kt | 2 +- .../admin}/member/CreatorAdminMemberService.kt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename src/main/kotlin/kr/co/vividnext/sodalive/{creator_admin => creator/admin}/content/CreatorAdminContentController.kt (97%) rename src/main/kotlin/kr/co/vividnext/sodalive/{creator_admin => creator/admin}/content/CreatorAdminContentRepository.kt (98%) rename src/main/kotlin/kr/co/vividnext/sodalive/{creator_admin => creator/admin}/content/CreatorAdminContentService.kt (98%) rename src/main/kotlin/kr/co/vividnext/sodalive/{creator_admin => creator/admin}/content/GetCreatorAdminContentListResponse.kt (91%) rename src/main/kotlin/kr/co/vividnext/sodalive/{creator_admin => creator/admin}/member/CreatorAdminMemberController.kt (96%) rename src/main/kotlin/kr/co/vividnext/sodalive/{creator_admin => creator/admin}/member/CreatorAdminMemberService.kt (98%) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentController.kt similarity index 97% rename from src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentController.kt rename to src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentController.kt index 47b2491..90e77b4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentController.kt @@ -1,4 +1,4 @@ -package kr.co.vividnext.sodalive.creator_admin.content +package kr.co.vividnext.sodalive.creator.admin.content import kr.co.vividnext.sodalive.admin.content.UpdateAdminContentRequest import kr.co.vividnext.sodalive.common.ApiResponse diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt similarity index 98% rename from src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentRepository.kt rename to src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt index e93087a..a04ee3a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt @@ -1,4 +1,4 @@ -package kr.co.vividnext.sodalive.creator_admin.content +package kr.co.vividnext.sodalive.creator.admin.content import com.querydsl.core.types.dsl.DateTimePath import com.querydsl.core.types.dsl.Expressions diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentService.kt similarity index 98% rename from src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentService.kt rename to src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentService.kt index faf23c0..bb2ddc4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/CreatorAdminContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentService.kt @@ -1,4 +1,4 @@ -package kr.co.vividnext.sodalive.creator_admin.content +package kr.co.vividnext.sodalive.creator.admin.content import kr.co.vividnext.sodalive.admin.content.UpdateAdminContentRequest import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/GetCreatorAdminContentListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/GetCreatorAdminContentListResponse.kt similarity index 91% rename from src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/GetCreatorAdminContentListResponse.kt rename to src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/GetCreatorAdminContentListResponse.kt index efc86d6..a466894 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/content/GetCreatorAdminContentListResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/GetCreatorAdminContentListResponse.kt @@ -1,4 +1,4 @@ -package kr.co.vividnext.sodalive.creator_admin.content +package kr.co.vividnext.sodalive.creator.admin.content import com.querydsl.core.annotations.QueryProjection diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/member/CreatorAdminMemberController.kt similarity index 96% rename from src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberController.kt rename to src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/member/CreatorAdminMemberController.kt index f64773c..3f14263 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/member/CreatorAdminMemberController.kt @@ -1,4 +1,4 @@ -package kr.co.vividnext.sodalive.creator_admin.member +package kr.co.vividnext.sodalive.creator.admin.member import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/member/CreatorAdminMemberService.kt similarity index 98% rename from src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberService.kt rename to src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/member/CreatorAdminMemberService.kt index 9ecff6e..3e193e9 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator_admin/member/CreatorAdminMemberService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/member/CreatorAdminMemberService.kt @@ -1,4 +1,4 @@ -package kr.co.vividnext.sodalive.creator_admin.member +package kr.co.vividnext.sodalive.creator.admin.member import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException -- 2.40.1 From f49203cafc63a10226e8b155ec2c45d45cc85713 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Aug 2023 21:38:04 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=ED=81=AC=EB=A6=AC=EC=97=90=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EA=B4=80=EB=A6=AC=EC=9E=90=20-=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EB=B3=80=EA=B2=BD=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/CreatorAdminContentController.kt | 9 +++--- .../content/CreatorAdminContentRepository.kt | 15 +++++++++ .../content/CreatorAdminContentService.kt | 31 +++++++++++++++---- .../UpdateCreatorAdminContentRequest.kt | 10 ++++++ 4 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/UpdateCreatorAdminContentRequest.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentController.kt index 90e77b4..f19e973 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentController.kt @@ -1,6 +1,5 @@ package kr.co.vividnext.sodalive.creator.admin.content -import kr.co.vividnext.sodalive.admin.content.UpdateAdminContentRequest import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.member.Member @@ -9,10 +8,11 @@ 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.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.RequestPart import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile @RestController @PreAuthorize("hasRole('CREATOR')") @@ -41,11 +41,12 @@ class CreatorAdminContentController(private val service: CreatorAdminContentServ @PutMapping fun modifyAudioContent( - @RequestBody request: UpdateAdminContentRequest, + @RequestPart("coverImage", required = false) coverImage: MultipartFile? = null, + @RequestPart("request") requestString: String, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { if (member == null) throw SodaException("로그인 정보를 확인해주세요.") - ApiResponse.ok(service.updateAudioContent(request, member)) + ApiResponse.ok(service.updateAudioContent(coverImage, requestString, member)) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt index a04ee3a..be9a34f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt @@ -9,6 +9,7 @@ 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.theme.QAudioContentTheme.audioContentTheme +import kr.co.vividnext.sodalive.member.QMember.member import org.springframework.data.jpa.repository.JpaRepository import java.time.LocalDateTime @@ -24,6 +25,8 @@ interface CreatorAdminAudioContentQueryRepository { ): List fun getHashTagList(audioContentId: Long): List + + fun getAudioContent(memberId: Long, audioContentId: Long): AudioContent? } class CreatorAdminAudioContentQueryRepositoryImpl( @@ -107,6 +110,18 @@ class CreatorAdminAudioContentQueryRepositoryImpl( .fetch() } + override fun getAudioContent(memberId: Long, audioContentId: Long): AudioContent? { + return queryFactory + .selectFrom(audioContent) + .innerJoin(audioContent.member, member) + .where( + member.id.eq(memberId) + .and(audioContent.id.eq(audioContentId)) + ) + .orderBy(audioContent.id.desc()) + .fetchFirst() + } + private fun formattedDateExpression( dateTime: DateTimePath, format: String = "%Y-%m-%d" diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentService.kt index bb2ddc4..9646bdb 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentService.kt @@ -1,19 +1,27 @@ package kr.co.vividnext.sodalive.creator.admin.content -import kr.co.vividnext.sodalive.admin.content.UpdateAdminContentRequest +import com.amazonaws.services.s3.model.ObjectMetadata +import com.fasterxml.jackson.databind.ObjectMapper import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront +import kr.co.vividnext.sodalive.aws.s3.S3Uploader 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.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 CreatorAdminContentService( private val repository: CreatorAdminContentRepository, private val audioContentCloudFront: AudioContentCloudFront, + private val objectMapper: ObjectMapper, + private val s3Uploader: S3Uploader, + + @Value("\${cloud.aws.s3.bucket}") + private val bucket: String, @Value("\${cloud.aws.cloud-front.host}") private val coverImageHost: String @@ -90,12 +98,23 @@ class CreatorAdminContentService( } @Transactional - fun updateAudioContent(request: UpdateAdminContentRequest, member: Member) { - val audioContent = repository.findByIdOrNull(id = request.id) + fun updateAudioContent(coverImage: MultipartFile?, requestString: String, member: Member) { + val request = objectMapper.readValue(requestString, UpdateCreatorAdminContentRequest::class.java) + val audioContent = repository.getAudioContent(memberId = member.id!!, audioContentId = request.id) ?: throw SodaException("잘못된 콘텐츠 입니다.") - if (request.isDefaultCoverImage) { - audioContent.coverImage = "profile/default_profile.png" + if (coverImage != null) { + val metadata = ObjectMetadata() + metadata.contentLength = coverImage.size + + val fileName = generateFileName() + val imagePath = s3Uploader.upload( + inputStream = coverImage.inputStream, + bucket = bucket, + filePath = "audio_content_cover/${request.id}/$fileName", + metadata = metadata + ) + audioContent.coverImage = imagePath } if (request.isActive != null) { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/UpdateCreatorAdminContentRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/UpdateCreatorAdminContentRequest.kt new file mode 100644 index 0000000..b1f85e1 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/UpdateCreatorAdminContentRequest.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.creator.admin.content + +data class UpdateCreatorAdminContentRequest( + val id: Long, + val title: String?, + val detail: String?, + val isAdult: Boolean?, + val isActive: Boolean?, + val isCommentAvailable: Boolean? +) -- 2.40.1 From eaeca443cf253a545569d6a466ea3f7b19f47239 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Aug 2023 22:38:57 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=ED=81=AC=EB=A6=AC=EC=97=90=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=BD=98=ED=85=90?= =?UTF-8?q?=EC=B8=A0=20=EC=A1=B0=ED=9A=8C=20-=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EA=B0=80=EB=8A=A5=20=EC=97=AC=EB=B6=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../creator/admin/content/CreatorAdminContentRepository.kt | 1 + .../creator/admin/content/GetCreatorAdminContentListResponse.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt index be9a34f..6067cc5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentRepository.kt @@ -82,6 +82,7 @@ class CreatorAdminAudioContentQueryRepositoryImpl( audioContentTheme.theme, audioContent.price, audioContent.isAdult, + audioContent.isCommentAvailable, audioContent.duration, audioContent.content, formattedDateExpression(audioContent.createdAt) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/GetCreatorAdminContentListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/GetCreatorAdminContentListResponse.kt index a466894..7284b11 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/GetCreatorAdminContentListResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/GetCreatorAdminContentListResponse.kt @@ -16,6 +16,7 @@ data class GetCreatorAdminContentListItem @QueryProjection constructor( val theme: String, val price: Int, val isAdult: Boolean, + val isCommentAvailable: Boolean, val remainingTime: String, var contentUrl: String, val date: String -- 2.40.1