test #123
| @@ -65,6 +65,7 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) { | ||||
|             .where( | ||||
|                 order.createdAt.goe(startDate) | ||||
|                     .and(order.createdAt.loe(endDate)) | ||||
|                     .and(order.isActive.isTrue) | ||||
|             ) | ||||
|             .groupBy(audioContent.id, order.type, orderFormattedDate, order.can) | ||||
|             .orderBy(member.id.desc(), orderFormattedDate.desc(), audioContent.id.asc()) | ||||
|   | ||||
| @@ -105,6 +105,7 @@ class AudioContentController(private val service: AudioContentService) { | ||||
|     fun getAudioContentList( | ||||
|         @RequestParam("creator-id") creatorId: Long, | ||||
|         @RequestParam("sort-type", required = false) sortType: SortType = SortType.NEWEST, | ||||
|         @RequestParam("category-id", required = false) categoryId: Long? = 0, | ||||
|         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, | ||||
|         pageable: Pageable | ||||
|     ) = run { | ||||
| @@ -114,6 +115,7 @@ class AudioContentController(private val service: AudioContentService) { | ||||
|             service.getAudioContentList( | ||||
|                 creatorId = creatorId, | ||||
|                 sortType = sortType, | ||||
|                 categoryId = categoryId ?: 0, | ||||
|                 member = member, | ||||
|                 offset = pageable.offset, | ||||
|                 limit = pageable.pageSize.toLong() | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import com.querydsl.core.types.dsl.Expressions | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | ||||
| import kr.co.vividnext.sodalive.content.QBundleAudioContent.bundleAudioContent | ||||
| import kr.co.vividnext.sodalive.content.category.QCategoryContent.categoryContent | ||||
| import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment | ||||
| import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike | ||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||
| @@ -37,6 +38,7 @@ interface AudioContentQueryRepository { | ||||
|         coverImageHost: String, | ||||
|         isAdult: Boolean = false, | ||||
|         sortType: SortType = SortType.NEWEST, | ||||
|         categoryId: Long = 0, | ||||
|         offset: Long = 0, | ||||
|         limit: Long = 10 | ||||
|     ): List<GetAudioContentListItem> | ||||
| @@ -144,6 +146,7 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) | ||||
|         coverImageHost: String, | ||||
|         isAdult: Boolean, | ||||
|         sortType: SortType, | ||||
|         categoryId: Long, | ||||
|         offset: Long, | ||||
|         limit: Long | ||||
|     ): List<GetAudioContentListItem> { | ||||
| @@ -163,6 +166,10 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) | ||||
|             where = where.and(audioContent.isAdult.isFalse) | ||||
|         } | ||||
|  | ||||
|         if (categoryId > 0) { | ||||
|             where = where.and(categoryContent.category.id.eq(categoryId)) | ||||
|         } | ||||
|  | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetAudioContentListItem( | ||||
| @@ -180,6 +187,8 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) | ||||
|                 ) | ||||
|             ) | ||||
|             .from(audioContent) | ||||
|             .leftJoin(categoryContent) | ||||
|             .on(audioContent.id.eq(categoryContent.content.id).and(categoryContent.isActive.ne(false))) | ||||
|             .leftJoin(pinContent) | ||||
|             .on(audioContent.id.eq(pinContent.content.id).and(pinContent.isActive.ne(false))) | ||||
|             .where(where) | ||||
|   | ||||
| @@ -624,6 +624,7 @@ class AudioContentService( | ||||
|         creatorId: Long, | ||||
|         sortType: SortType, | ||||
|         member: Member, | ||||
|         categoryId: Long = 0, | ||||
|         offset: Long, | ||||
|         limit: Long | ||||
|     ): GetAudioContentListResponse { | ||||
| @@ -637,6 +638,7 @@ class AudioContentService( | ||||
|             coverImageHost = coverImageHost, | ||||
|             isAdult = member.auth != null, | ||||
|             sortType = sortType, | ||||
|             categoryId = categoryId, | ||||
|             offset = offset, | ||||
|             limit = limit | ||||
|         ) | ||||
|   | ||||
| @@ -0,0 +1,19 @@ | ||||
| package kr.co.vividnext.sodalive.content.category | ||||
|  | ||||
| import kr.co.vividnext.sodalive.common.BaseEntity | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| import javax.persistence.Entity | ||||
| import javax.persistence.FetchType | ||||
| import javax.persistence.JoinColumn | ||||
| import javax.persistence.ManyToOne | ||||
|  | ||||
| @Entity | ||||
| data class Category( | ||||
|     var title: String, | ||||
|     var orders: Int = 1, | ||||
|     var isActive: Boolean = true | ||||
| ) : BaseEntity() { | ||||
|     @ManyToOne(fetch = FetchType.LAZY) | ||||
|     @JoinColumn(name = "member_id", nullable = false) | ||||
|     var member: Member? = null | ||||
| } | ||||
| @@ -0,0 +1,22 @@ | ||||
| package kr.co.vividnext.sodalive.content.category | ||||
|  | ||||
| import kr.co.vividnext.sodalive.common.BaseEntity | ||||
| import kr.co.vividnext.sodalive.content.AudioContent | ||||
| import javax.persistence.Entity | ||||
| import javax.persistence.FetchType | ||||
| import javax.persistence.JoinColumn | ||||
| import javax.persistence.ManyToOne | ||||
|  | ||||
| @Entity | ||||
| data class CategoryContent( | ||||
|     var orders: Int = 1, | ||||
|     var isActive: Boolean = true | ||||
| ) : BaseEntity() { | ||||
|     @ManyToOne(fetch = FetchType.LAZY) | ||||
|     @JoinColumn(name = "content_id", nullable = false) | ||||
|     var content: AudioContent? = null | ||||
|  | ||||
|     @ManyToOne(fetch = FetchType.LAZY) | ||||
|     @JoinColumn(name = "category_id", nullable = false) | ||||
|     var category: Category? = null | ||||
| } | ||||
| @@ -0,0 +1,32 @@ | ||||
| package kr.co.vividnext.sodalive.content.category | ||||
|  | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.content.category.QCategoryContent.categoryContent | ||||
| import org.springframework.data.jpa.repository.JpaRepository | ||||
|  | ||||
| interface CategoryContentRepository : JpaRepository<CategoryContent, Long>, CategoryContentQueryRepository | ||||
|  | ||||
| interface CategoryContentQueryRepository { | ||||
|     fun findByContentIdAndCategoryId(contentId: Long, categoryId: Long): CategoryContent? | ||||
|     fun deleteByCategoryId(categoryId: Long) | ||||
| } | ||||
|  | ||||
| class CategoryContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CategoryContentQueryRepository { | ||||
|     override fun findByContentIdAndCategoryId(contentId: Long, categoryId: Long): CategoryContent? { | ||||
|         return queryFactory | ||||
|             .selectFrom(categoryContent) | ||||
|             .where( | ||||
|                 categoryContent.content.id.eq(contentId) | ||||
|                     .and(categoryContent.category.id.eq(categoryId)) | ||||
|             ) | ||||
|             .fetchFirst() | ||||
|     } | ||||
|  | ||||
|     override fun deleteByCategoryId(categoryId: Long) { | ||||
|         queryFactory | ||||
|             .update(categoryContent) | ||||
|             .set(categoryContent.isActive, false) | ||||
|             .where(categoryContent.category.id.eq(categoryId)) | ||||
|             .execute() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,63 @@ | ||||
| package kr.co.vividnext.sodalive.content.category | ||||
|  | ||||
| import kr.co.vividnext.sodalive.common.ApiResponse | ||||
| import kr.co.vividnext.sodalive.common.SodaException | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| import org.springframework.security.access.prepost.PreAuthorize | ||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||
| import org.springframework.web.bind.annotation.DeleteMapping | ||||
| import org.springframework.web.bind.annotation.GetMapping | ||||
| import org.springframework.web.bind.annotation.PathVariable | ||||
| import org.springframework.web.bind.annotation.PostMapping | ||||
| import org.springframework.web.bind.annotation.PutMapping | ||||
| import org.springframework.web.bind.annotation.RequestBody | ||||
| import org.springframework.web.bind.annotation.RequestMapping | ||||
| import org.springframework.web.bind.annotation.RequestParam | ||||
| import org.springframework.web.bind.annotation.RestController | ||||
|  | ||||
| @RestController | ||||
| @RequestMapping("/category") | ||||
| class CategoryController(private val service: CategoryService) { | ||||
|     @PostMapping | ||||
|     @PreAuthorize("hasRole('CREATOR')") | ||||
|     fun createCategory( | ||||
|         @RequestBody request: CreateCategoryRequest, | ||||
|         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||
|     ) = run { | ||||
|         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||
|  | ||||
|         ApiResponse.ok(service.createCategory(request = request, member = member)) | ||||
|     } | ||||
|  | ||||
|     @PutMapping | ||||
|     @PreAuthorize("hasRole('CREATOR')") | ||||
|     fun modifyCategory( | ||||
|         @RequestBody request: ModifyCategoryRequest, | ||||
|         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||
|     ) = run { | ||||
|         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||
|  | ||||
|         ApiResponse.ok(service.modifyCategory(request = request, member = member)) | ||||
|     } | ||||
|  | ||||
|     @DeleteMapping("/{id}") | ||||
|     @PreAuthorize("hasRole('CREATOR')") | ||||
|     fun deleteCategory( | ||||
|         @PathVariable("id") categoryId: Long, | ||||
|         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||
|     ) = run { | ||||
|         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||
|  | ||||
|         ApiResponse.ok(service.deleteCategory(categoryId = categoryId, member = member)) | ||||
|     } | ||||
|  | ||||
|     @GetMapping | ||||
|     fun getCategoryList( | ||||
|         @RequestParam creatorId: Long, | ||||
|         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||
|     ) = run { | ||||
|         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||
|  | ||||
|         ApiResponse.ok(service.getCategoryList(creatorId = creatorId, memberId = member.id!!)) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,45 @@ | ||||
| package kr.co.vividnext.sodalive.content.category | ||||
|  | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.content.category.QCategory.category | ||||
| import org.springframework.data.jpa.repository.JpaRepository | ||||
|  | ||||
| interface CategoryRepository : JpaRepository<Category, Long>, CategoryQueryRepository | ||||
|  | ||||
| interface CategoryQueryRepository { | ||||
|     fun findByTitleAndMemberId(title: String, memberId: Long): Category? | ||||
|  | ||||
|     fun findByIdAndMemberId(categoryId: Long, memberId: Long): Category? | ||||
|  | ||||
|     fun findByCreatorId(creatorId: Long): List<GetCategoryListResponse> | ||||
| } | ||||
|  | ||||
| class CategoryQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CategoryQueryRepository { | ||||
|     override fun findByTitleAndMemberId(title: String, memberId: Long): Category? { | ||||
|         return queryFactory | ||||
|             .selectFrom(category) | ||||
|             .where( | ||||
|                 category.title.eq(title) | ||||
|                     .and(category.member.id.eq(memberId)) | ||||
|             ) | ||||
|             .fetchFirst() | ||||
|     } | ||||
|  | ||||
|     override fun findByIdAndMemberId(categoryId: Long, memberId: Long): Category? { | ||||
|         return queryFactory | ||||
|             .selectFrom(category) | ||||
|             .where( | ||||
|                 category.id.eq(categoryId) | ||||
|                     .and(category.member.id.eq(memberId)) | ||||
|             ) | ||||
|             .fetchFirst() | ||||
|     } | ||||
|  | ||||
|     override fun findByCreatorId(creatorId: Long): List<GetCategoryListResponse> { | ||||
|         return queryFactory | ||||
|             .select(QGetCategoryListResponse(category.id, category.title)) | ||||
|             .from(category) | ||||
|             .where(category.member.id.eq(creatorId)) | ||||
|             .fetch() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,99 @@ | ||||
| package kr.co.vividnext.sodalive.content.category | ||||
|  | ||||
| import kr.co.vividnext.sodalive.common.SodaException | ||||
| import kr.co.vividnext.sodalive.content.AudioContentRepository | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| import kr.co.vividnext.sodalive.member.block.BlockMemberRepository | ||||
| import org.springframework.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | ||||
|  | ||||
| @Service | ||||
| class CategoryService( | ||||
|     private val repository: CategoryRepository, | ||||
|     private val contentRepository: AudioContentRepository, | ||||
|     private val blockMemberRepository: BlockMemberRepository, | ||||
|     private val categoryContentRepository: CategoryContentRepository | ||||
| ) { | ||||
|     @Transactional | ||||
|     fun createCategory(request: CreateCategoryRequest, member: Member) { | ||||
|         validateTitle(title = request.title) | ||||
|         val category = repository.findByTitleAndMemberId(title = request.title, memberId = member.id!!) | ||||
|             ?: repository.save( | ||||
|                 Category(title = request.title).apply { | ||||
|                     this.member = member | ||||
|                 } | ||||
|             ) | ||||
|         category.isActive = true | ||||
|  | ||||
|         for (contentId in request.contentIdList) { | ||||
|             val content = contentRepository.findByIdAndActive(contentId = contentId) | ||||
|                 ?: continue | ||||
|  | ||||
|             val categoryContent = categoryContentRepository.findByContentIdAndCategoryId( | ||||
|                 contentId = contentId, | ||||
|                 categoryId = category.id!! | ||||
|             ) ?: categoryContentRepository.save( | ||||
|                 CategoryContent().apply { | ||||
|                     this.content = content | ||||
|                     this.category = category | ||||
|                 } | ||||
|             ) | ||||
|             categoryContent.isActive = true | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
|     fun modifyCategory(request: ModifyCategoryRequest, member: Member) { | ||||
|         val category = repository.findByIdAndMemberId(categoryId = request.categoryId, memberId = member.id!!) | ||||
|             ?: throw SodaException("잘못된 요청입니다.") | ||||
|  | ||||
|         if (!request.title.isNullOrBlank()) { | ||||
|             validateTitle(title = request.title) | ||||
|             category.title = request.title | ||||
|         } | ||||
|  | ||||
|         for (contentId in request.addContentIdList) { | ||||
|             val content = contentRepository.findByIdAndActive(contentId = contentId) | ||||
|                 ?: continue | ||||
|  | ||||
|             val categoryContent = categoryContentRepository.findByContentIdAndCategoryId( | ||||
|                 contentId = contentId, | ||||
|                 categoryId = category.id!! | ||||
|             ) ?: categoryContentRepository.save( | ||||
|                 CategoryContent().apply { | ||||
|                     this.content = content | ||||
|                     this.category = category | ||||
|                 } | ||||
|             ) | ||||
|             categoryContent.isActive = true | ||||
|         } | ||||
|  | ||||
|         for (contentId in request.removeContentIdList) { | ||||
|             val categoryContent = categoryContentRepository.findByContentIdAndCategoryId( | ||||
|                 contentId = contentId, | ||||
|                 categoryId = category.id!! | ||||
|             ) ?: continue | ||||
|             categoryContent.isActive = false | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
|     fun deleteCategory(categoryId: Long, member: Member) { | ||||
|         val category = repository.findByIdAndMemberId(categoryId = categoryId, memberId = member.id!!) | ||||
|             ?: throw SodaException("잘못된 요청입니다.") | ||||
|         category.isActive = false | ||||
|  | ||||
|         categoryContentRepository.deleteByCategoryId(categoryId = categoryId) | ||||
|     } | ||||
|  | ||||
|     fun getCategoryList(creatorId: Long, memberId: Long): List<GetCategoryListResponse> { | ||||
|         val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = creatorId) | ||||
|         if (isBlocked) throw SodaException("잘못된 접근입니다.") | ||||
|  | ||||
|         return repository.findByCreatorId(creatorId = creatorId) | ||||
|     } | ||||
|  | ||||
|     private fun validateTitle(title: String) { | ||||
|         if (title.length < 2) throw SodaException("카테고리명은 2글자 이상 입력하세요") | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,6 @@ | ||||
| package kr.co.vividnext.sodalive.content.category | ||||
|  | ||||
| data class CreateCategoryRequest( | ||||
|     val title: String, | ||||
|     val contentIdList: List<Long> | ||||
| ) | ||||
| @@ -0,0 +1,8 @@ | ||||
| package kr.co.vividnext.sodalive.content.category | ||||
|  | ||||
| import com.querydsl.core.annotations.QueryProjection | ||||
|  | ||||
| data class GetCategoryListResponse @QueryProjection constructor( | ||||
|     val categoryId: Long, | ||||
|     val category: String | ||||
| ) | ||||
| @@ -0,0 +1,8 @@ | ||||
| package kr.co.vividnext.sodalive.content.category | ||||
|  | ||||
| data class ModifyCategoryRequest( | ||||
|     val categoryId: Long, | ||||
|     val title: String?, | ||||
|     val addContentIdList: List<Long>, | ||||
|     val removeContentIdList: List<Long> | ||||
| ) | ||||
| @@ -116,16 +116,14 @@ class CreatorAdminCalculateQueryRepository(private val queryFactory: JPAQueryFac | ||||
|                     order.can.sum() | ||||
|                 ) | ||||
|             ) | ||||
|             .from(useCanCalculate) | ||||
|             .innerJoin(useCanCalculate.useCan, useCan) | ||||
|             .innerJoin(useCan.order, order) | ||||
|             .from(order) | ||||
|             .innerJoin(order.audioContent, audioContent) | ||||
|             .innerJoin(audioContent.member, member) | ||||
|             .where( | ||||
|                 useCanCalculate.status.eq(UseCanCalculateStatus.RECEIVED) | ||||
|                     .and(useCanCalculate.recipientCreatorId.eq(memberId)) | ||||
|                     .and(order.createdAt.goe(startDate)) | ||||
|                 order.createdAt.goe(startDate) | ||||
|                     .and(order.createdAt.loe(endDate)) | ||||
|                     .and(order.isActive.isTrue) | ||||
|                     .and(order.creator.id.eq(memberId)) | ||||
|             ) | ||||
|             .groupBy(audioContent.id, order.type, orderFormattedDate, order.can) | ||||
|             .offset(offset) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user