diff --git a/README.md b/README.md index bbe0eb66..294fd39c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # Server -> 스터디 관리 서비스 "가지" 서버 +> 스터디 관리 서비스 "가지" 서버입니다 diff --git a/src/main/java/gaji/service/domain/common/converter/CategoryConverter.java b/src/main/java/gaji/service/domain/common/converter/CategoryConverter.java index 1d122843..1d5ded03 100644 --- a/src/main/java/gaji/service/domain/common/converter/CategoryConverter.java +++ b/src/main/java/gaji/service/domain/common/converter/CategoryConverter.java @@ -5,6 +5,8 @@ import gaji.service.domain.common.web.dto.CategoryResponseDTO; import gaji.service.domain.enums.CategoryEnum; import gaji.service.domain.enums.PostTypeEnum; +import gaji.service.global.exception.RestApiException; +import gaji.service.global.exception.code.status.GlobalErrorStatus; import org.springframework.core.convert.converter.Converter; import java.util.List; @@ -15,7 +17,11 @@ public class CategoryConverter implements Converter { // @RequestParam으로 String->CategoryEnum으로 convert할 때 필요한 메서드 @Override public CategoryEnum convert(String source) { - return CategoryEnum.from(source); + CategoryEnum category = CategoryEnum.from(source); + if (category == null) { + throw new RestApiException(GlobalErrorStatus._INVALID_CATEGORY); + } + return category; } public static Category toCategory(CategoryEnum category) { diff --git a/src/main/java/gaji/service/domain/common/repository/CategoryRepository.java b/src/main/java/gaji/service/domain/common/repository/CategoryRepository.java index b78207cb..640af22f 100644 --- a/src/main/java/gaji/service/domain/common/repository/CategoryRepository.java +++ b/src/main/java/gaji/service/domain/common/repository/CategoryRepository.java @@ -4,8 +4,11 @@ import gaji.service.domain.enums.CategoryEnum; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface CategoryRepository extends JpaRepository { Category findByCategory(CategoryEnum category); boolean existsByCategory(CategoryEnum category); boolean existsById(Long id); + List findAllByCategory(CategoryEnum category); } diff --git a/src/main/java/gaji/service/domain/common/repository/SelectCategoryQueryDslRepository.java b/src/main/java/gaji/service/domain/common/repository/SelectCategoryQueryDslRepository.java index 17acdab6..64f40d59 100644 --- a/src/main/java/gaji/service/domain/common/repository/SelectCategoryQueryDslRepository.java +++ b/src/main/java/gaji/service/domain/common/repository/SelectCategoryQueryDslRepository.java @@ -7,6 +7,6 @@ import java.util.List; public interface SelectCategoryQueryDslRepository { - List findAllFetchJoinWithCategoryByEntityIdAndPostType(Long entityId, PostTypeEnum postType); + SelectCategory findOneFetchJoinWithCategoryByEntityIdAndPostType(Long entityId, PostTypeEnum postType); List findEntityIdListByCategoryAndPostType(Category category, PostTypeEnum postType); } diff --git a/src/main/java/gaji/service/domain/common/repository/SelectCategoryQueryDslRepositoryImpl.java b/src/main/java/gaji/service/domain/common/repository/SelectCategoryQueryDslRepositoryImpl.java index 40a502f2..96e7128e 100644 --- a/src/main/java/gaji/service/domain/common/repository/SelectCategoryQueryDslRepositoryImpl.java +++ b/src/main/java/gaji/service/domain/common/repository/SelectCategoryQueryDslRepositoryImpl.java @@ -20,7 +20,7 @@ public class SelectCategoryQueryDslRepositoryImpl implements SelectCategoryQuery private final JPAQueryFactory jpaQueryFactory; @Override - public List findAllFetchJoinWithCategoryByEntityIdAndPostType(Long entityId, PostTypeEnum postType) { + public SelectCategory findOneFetchJoinWithCategoryByEntityIdAndPostType(Long entityId, PostTypeEnum postType) { return jpaQueryFactory .selectFrom(selectCategory) .join(selectCategory.category, category1) @@ -29,8 +29,8 @@ public List findAllFetchJoinWithCategoryByEntityIdAndPostType(Lo selectCategory.entityId.eq(entityId), selectCategory.type.eq(postType) ) - .orderBy(selectCategory.id.asc()) - .fetch(); + .fetchOne() + ; } @Override diff --git a/src/main/java/gaji/service/domain/common/service/CategoryService.java b/src/main/java/gaji/service/domain/common/service/CategoryService.java index c3b23be7..3618d7de 100644 --- a/src/main/java/gaji/service/domain/common/service/CategoryService.java +++ b/src/main/java/gaji/service/domain/common/service/CategoryService.java @@ -16,10 +16,13 @@ public interface CategoryService { List findEntityIdListByCategoryIdAndPostType(Long categoryId, PostTypeEnum postType); boolean existsByCategory(CategoryEnum category); boolean existsByCategoryId(Long categoryId); - List findAllFetchJoinWithCategoryByEntityIdAndPostType(Long entityId, PostTypeEnum postType); + SelectCategory findOneFetchJoinWithCategoryByEntityIdAndPostType(Long entityId, PostTypeEnum postType); List findAllCategory(); void saveAllSelectCategory(List selectCategoryList); SelectCategory findByEntityIdAndType(Long entityId, PostTypeEnum type); + + List findAllByCategory(CategoryEnum category); + } diff --git a/src/main/java/gaji/service/domain/common/service/CategoryServiceImpl.java b/src/main/java/gaji/service/domain/common/service/CategoryServiceImpl.java index d0a3a561..524da346 100644 --- a/src/main/java/gaji/service/domain/common/service/CategoryServiceImpl.java +++ b/src/main/java/gaji/service/domain/common/service/CategoryServiceImpl.java @@ -39,6 +39,12 @@ public Category findByCategory(CategoryEnum category) { return categoryRepository.findByCategory(category); } + //todo: 나중에 카테고리 DB에 통일 하면 지워야함 + @Override + public List findAllByCategory(CategoryEnum category) { + return categoryRepository.findAllByCategory(category); + } + @Override public Category findByCategoryId(Long categoryId) { return categoryRepository.findById(categoryId) @@ -61,8 +67,8 @@ public boolean existsByCategoryId(Long categoryId) { } @Override - public List findAllFetchJoinWithCategoryByEntityIdAndPostType(Long entityId, PostTypeEnum postType) { - return selectCategoryRepository.findAllFetchJoinWithCategoryByEntityIdAndPostType(entityId, postType); + public SelectCategory findOneFetchJoinWithCategoryByEntityIdAndPostType(Long entityId, PostTypeEnum postType) { + return selectCategoryRepository.findOneFetchJoinWithCategoryByEntityIdAndPostType(entityId, postType); } @Override diff --git a/src/main/java/gaji/service/domain/common/validation/CategoryExistsValidator.java b/src/main/java/gaji/service/domain/common/validation/CategoryExistsValidator.java index 545b8f4f..9bee765e 100644 --- a/src/main/java/gaji/service/domain/common/validation/CategoryExistsValidator.java +++ b/src/main/java/gaji/service/domain/common/validation/CategoryExistsValidator.java @@ -2,17 +2,17 @@ import gaji.service.domain.common.annotation.ExistsCategory; import gaji.service.domain.common.service.CategoryService; +import gaji.service.domain.enums.CategoryEnum; import gaji.service.global.exception.code.status.GlobalErrorStatus; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import java.util.List; @Component @RequiredArgsConstructor -public class CategoryExistsValidator implements ConstraintValidator> { +public class CategoryExistsValidator implements ConstraintValidator { private final CategoryService categoryService; @Override @@ -21,9 +21,8 @@ public void initialize(ExistsCategory constraintAnnotation) { } @Override - public boolean isValid(List values, ConstraintValidatorContext context) { - boolean isValid = values.stream() - .allMatch(value -> categoryService.existsByCategoryId(value)); + public boolean isValid(String value, ConstraintValidatorContext context) { + boolean isValid = categoryService.existsByCategory(CategoryEnum.from(value)); if (!isValid) { context.disableDefaultConstraintViolation(); diff --git a/src/main/java/gaji/service/domain/enums/CategoryEnum.java b/src/main/java/gaji/service/domain/enums/CategoryEnum.java index 8ba1882a..4fbd43b8 100644 --- a/src/main/java/gaji/service/domain/enums/CategoryEnum.java +++ b/src/main/java/gaji/service/domain/enums/CategoryEnum.java @@ -14,11 +14,12 @@ public enum CategoryEnum { AI("인공지능"), HW("하드웨어"), SECURITY("보안"), - NETWORK("클라우드 네트워크"), + NETWORK("네트워크-클라우드"), LANGUAGE("어학"), DESIGN("디자인"), BUSINESS("비즈니스"), - BOOK("독서 모임"); + BOOK("독서"), + ETC("기타"); @JsonValue private final String value; @@ -35,6 +36,15 @@ public static CategoryEnum from(String param) { } } log.error("CategoryEnum.from() exception occur param: {}", param); - throw new RestApiException(GlobalErrorStatus._INVALID_CATEGORY); + return null; + } + + public static CategoryEnum fromValue(String value) { + for (CategoryEnum category : CategoryEnum.values()) { + if (category.value.equals(value)) { + return category; + } + } + throw new IllegalArgumentException("Unknown category: " + value); } } \ No newline at end of file diff --git a/src/main/java/gaji/service/domain/post/converter/CommunityCommentConverter.java b/src/main/java/gaji/service/domain/post/converter/CommunityCommentConverter.java index 5ab364d8..615ed1d6 100644 --- a/src/main/java/gaji/service/domain/post/converter/CommunityCommentConverter.java +++ b/src/main/java/gaji/service/domain/post/converter/CommunityCommentConverter.java @@ -1,22 +1,28 @@ package gaji.service.domain.post.converter; import gaji.service.domain.post.entity.CommunityComment; +import gaji.service.domain.post.service.CommunityCommentService; import gaji.service.domain.post.web.dto.CommunityPostCommentResponseDTO; import gaji.service.global.converter.DateConverter; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; +@RequiredArgsConstructor +@Component public class CommunityCommentConverter { + private final CommunityCommentService communityCommentService; - public static CommunityPostCommentResponseDTO.WriteCommentDTO toWriteCommentDTO(CommunityComment comment) { - return CommunityPostCommentResponseDTO.WriteCommentDTO.builder() + public static CommunityPostCommentResponseDTO.WriteCommentResponseDTO toWriteCommentResponseDTO(CommunityComment comment) { + return CommunityPostCommentResponseDTO.WriteCommentResponseDTO.builder() .commentId(comment.getId()) .build(); } - public static CommunityPostCommentResponseDTO.PostCommentDTO toPostCommentDTO(CommunityComment comment) { + public static CommunityPostCommentResponseDTO.PostCommentDTO toPostCommentDTO(CommunityComment comment, boolean isWriter) { return CommunityPostCommentResponseDTO.PostCommentDTO.builder() .commentId(comment.getId()) .userId(comment.getUser().getId()) @@ -24,14 +30,18 @@ public static CommunityPostCommentResponseDTO.PostCommentDTO toPostCommentDTO(Co .body(comment.getBody()) .groupNum(comment.getGroupNum()) .depth(comment.getDepth()) + .isWriter(isWriter) .createdAt(DateConverter.convertWriteTimeFormat(LocalDate.from(comment.getCreatedAt()), " 작성")) .build(); } - public static CommunityPostCommentResponseDTO.PostCommentListDTO toPostCommentListDTO(List commentList, boolean hasNext) { - List postCommentDTOList = commentList.stream() - .map(CommunityCommentConverter::toPostCommentDTO) - .collect(Collectors.toList()); + public CommunityPostCommentResponseDTO.PostCommentListDTO toPostCommentListDTO(List commentList, boolean hasNext, Long userId) { + List postCommentDTOList = new ArrayList<>(); + + for (CommunityComment communityComment : commentList) { + boolean isWriter = (userId == null) ? false : communityCommentService.isCommentWriter(userId, communityComment); + postCommentDTOList.add(CommunityCommentConverter.toPostCommentDTO(communityComment, isWriter)); + } return CommunityPostCommentResponseDTO.PostCommentListDTO.builder() .commentList(postCommentDTOList) diff --git a/src/main/java/gaji/service/domain/post/converter/CommunityPostConverter.java b/src/main/java/gaji/service/domain/post/converter/CommunityPostConverter.java index 7c35cc03..ed0bd72e 100644 --- a/src/main/java/gaji/service/domain/post/converter/CommunityPostConverter.java +++ b/src/main/java/gaji/service/domain/post/converter/CommunityPostConverter.java @@ -1,9 +1,13 @@ package gaji.service.domain.post.converter; import gaji.service.domain.common.converter.HashtagConverter; +import gaji.service.domain.common.entity.Category; +import gaji.service.domain.common.entity.SelectCategory; import gaji.service.domain.common.entity.SelectHashtag; +import gaji.service.domain.common.service.CategoryService; import gaji.service.domain.common.service.HashtagService; import gaji.service.domain.common.web.dto.HashtagResponseDTO; +import gaji.service.domain.enums.CategoryEnum; import gaji.service.domain.enums.PostStatusEnum; import gaji.service.domain.enums.PostTypeEnum; import gaji.service.domain.post.entity.CommnuityPost; @@ -12,8 +16,9 @@ import gaji.service.domain.post.entity.PostLikes; import gaji.service.domain.post.service.CommunityPostBookMarkService; import gaji.service.domain.post.service.CommunityPostLikesService; +import gaji.service.domain.post.service.CommunityPostQueryService; import gaji.service.domain.post.web.dto.CommunityPostResponseDTO; -import gaji.service.domain.post.web.dto.PostRequestDTO; +import gaji.service.domain.post.web.dto.CommunityPostRequestDTO; import gaji.service.domain.user.entity.User; import gaji.service.global.converter.DateConverter; import lombok.RequiredArgsConstructor; @@ -21,14 +26,17 @@ import java.time.LocalDate; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @RequiredArgsConstructor @Component public class CommunityPostConverter { private final HashtagService hashtagService; + private final CategoryService categoryService; private final CommunityPostBookMarkService postBookMarkService; private final CommunityPostLikesService postLikesService; + private final CommunityPostQueryService communityPostQueryService; // 초기 PostStatus 지정 public static PostStatusEnum getInitialPostStatus(PostTypeEnum type) { @@ -36,8 +44,8 @@ public static PostStatusEnum getInitialPostStatus(PostTypeEnum type) { (type == PostTypeEnum.PROJECT) ? PostStatusEnum.RECRUITING : PostStatusEnum.BLOGING; } - public static CommunityPostResponseDTO.UploadPostDTO toUploadPostDTO(CommnuityPost post) { - return CommunityPostResponseDTO.UploadPostDTO + public static CommunityPostResponseDTO.UploadPostResponseDTO toUploadPostResponseDTO(CommnuityPost post) { + return CommunityPostResponseDTO.UploadPostResponseDTO .builder() .postId(post.getId()) .build(); @@ -57,7 +65,7 @@ public static CommunityPostResponseDTO.PostLikesIdDTO toPostLikesIdDTO(PostLikes .build(); } - public static CommnuityPost toPost(PostRequestDTO.UploadPostDTO request, User user) { + public static CommnuityPost toPost(CommunityPostRequestDTO.UploadPostRequestDTO request, User user) { return CommnuityPost.builder() .user(user) .title(request.getTitle()) @@ -69,7 +77,7 @@ public static CommnuityPost toPost(PostRequestDTO.UploadPostDTO request, User us .build(); } - public static CommunityComment toComment(PostRequestDTO.WriteCommentDTO request, User user, CommnuityPost post, CommunityComment parentComment) { + public static CommunityComment toComment(CommunityPostRequestDTO.WriteCommentRequestDTO request, User user, CommnuityPost post, CommunityComment parentComment) { return CommunityComment.builder() .user(user) .post(post) @@ -107,12 +115,13 @@ public CommunityPostResponseDTO.PostPreviewDTO toPostPreviewDTO(CommnuityPost po .uploadTime(DateConverter.convertToRelativeTimeFormat(post.getCreatedAt())) .hit(post.getHit()) .popularityScore(post.getPopularityScore()) + .status(post.getStatus()) .hashtagList(hashtagList) .build(); } public CommunityPostResponseDTO.PostPreviewListDTO toPostPreviewListDTO(List postList, boolean hasNext) { - CommunityPostConverter postConverter = new CommunityPostConverter(hashtagService, postBookMarkService, postLikesService); + CommunityPostConverter postConverter = new CommunityPostConverter(hashtagService, categoryService, postBookMarkService, postLikesService, communityPostQueryService); List postPreviewDTOList = postList.stream() .map(postConverter::toPostPreviewDTO) .collect(Collectors.toList()); @@ -123,24 +132,37 @@ public CommunityPostResponseDTO.PostPreviewListDTO toPostPreviewListDTO(List selectHashtagList = hashtagService.findAllFetchJoinWithHashtagByEntityIdAndPostType(post.getId(), post.getType()); List hashtagNameAndIdDTOList = HashtagConverter.toHashtagNameAndIdDTOList(selectHashtagList); + + // ofNullable 메서드로 NPE 방지 + /*CategoryEnum category = Optional.ofNullable(categoryService.findOneFetchJoinWithCategoryByEntityIdAndPostType(post.getId(), post.getType())) + .map(SelectCategory::getCategory) + .map(Category::getCategory) + .orElse(null);*/ + boolean isBookmarked = (userId == null) ? false : postBookMarkService.existsByUserAndPost(userId, post); boolean isLiked = (userId == null) ? false : postLikesService.existsByUserAndPost(userId, post); + boolean isWriter = (userId == null) ? false : communityPostQueryService.isPostWriter(userId, post); return CommunityPostResponseDTO.PostDetailDTO.builder() .userId(post.getUser().getId()) .type(post.getType()) .createdAt(DateConverter.convertWriteTimeFormat(LocalDate.from(post.getCreatedAt()), "")) .hit(post.getHit()) + .likeCnt(post.getLikeCnt()) + .bookmarkCnt(post.getBookmarkCnt()) .commentCnt(post.getCommentCnt()) .userNickname(post.getUser().getNickname()) .title(post.getTitle()) .hashtagList(hashtagNameAndIdDTOList) - .isBookMarked(isBookmarked) - .isLiked(isLiked) + .isWriter(isWriter) + .bookMarkStatus(isBookmarked) + .likeStatus(isLiked) .body(post.getBody()) + .status(post.getStatus()) + .category(selectCategory.getCategory().getCategory().getValue()) .build(); } } diff --git a/src/main/java/gaji/service/domain/post/converter/PostStatusConverter.java b/src/main/java/gaji/service/domain/post/converter/PostStatusConverter.java index 06714ef2..844b0b40 100644 --- a/src/main/java/gaji/service/domain/post/converter/PostStatusConverter.java +++ b/src/main/java/gaji/service/domain/post/converter/PostStatusConverter.java @@ -10,7 +10,6 @@ public class PostStatusConverter implements Converter { @Override public PostStatusEnum convert(String param) { - if (!StringUtils.hasText(param)) throw new RestApiException(CommunityPostErrorStatus._INVALID_POST_STATUS); return PostStatusEnum.from(param); } } diff --git a/src/main/java/gaji/service/domain/post/converter/PostTypeConverter.java b/src/main/java/gaji/service/domain/post/converter/PostTypeConverter.java index 581d6bac..27b75508 100644 --- a/src/main/java/gaji/service/domain/post/converter/PostTypeConverter.java +++ b/src/main/java/gaji/service/domain/post/converter/PostTypeConverter.java @@ -10,7 +10,6 @@ public class PostTypeConverter implements Converter { @Override public PostTypeEnum convert(String param) { - if (!StringUtils.hasText(param)) throw new RestApiException(CommunityPostErrorStatus._INVALID_POST_TYPE); return PostTypeEnum.from(param); } } diff --git a/src/main/java/gaji/service/domain/post/converter/SortTypeConverter.java b/src/main/java/gaji/service/domain/post/converter/SortTypeConverter.java index ea813732..b8b2388f 100644 --- a/src/main/java/gaji/service/domain/post/converter/SortTypeConverter.java +++ b/src/main/java/gaji/service/domain/post/converter/SortTypeConverter.java @@ -10,7 +10,6 @@ public class SortTypeConverter implements Converter { @Override public SortType convert(String param) { - if (!StringUtils.hasText(param)) throw new RestApiException(GlobalErrorStatus._SORT_TYPE_NOT_VALID); return SortType.from(param); } } diff --git a/src/main/java/gaji/service/domain/post/repository/CommunityPostQueryDslRepository.java b/src/main/java/gaji/service/domain/post/repository/CommunityPostQueryDslRepository.java index b074160f..6eb994cc 100644 --- a/src/main/java/gaji/service/domain/post/repository/CommunityPostQueryDslRepository.java +++ b/src/main/java/gaji/service/domain/post/repository/CommunityPostQueryDslRepository.java @@ -13,7 +13,8 @@ public interface CommunityPostQueryDslRepository { - Slice findAllFetchJoinWithUser(Integer lastPopularityScore, + Slice findAllFetchJoinWithUser(String keyword, + Integer lastPopularityScore, Long lastPostId, Integer lastLikeCnt, Integer lastHit, diff --git a/src/main/java/gaji/service/domain/post/repository/CommunityPostQueryDslRepositoryImpl.java b/src/main/java/gaji/service/domain/post/repository/CommunityPostQueryDslRepositoryImpl.java index 36deb533..19658c31 100644 --- a/src/main/java/gaji/service/domain/post/repository/CommunityPostQueryDslRepositoryImpl.java +++ b/src/main/java/gaji/service/domain/post/repository/CommunityPostQueryDslRepositoryImpl.java @@ -3,6 +3,8 @@ import com.querydsl.core.Tuple; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.EnumPath; +import com.querydsl.core.types.dsl.NumberPath; import com.querydsl.jpa.impl.JPAQueryFactory; import gaji.service.domain.common.service.CategoryService; import gaji.service.domain.enums.PostStatusEnum; @@ -19,6 +21,8 @@ import java.time.LocalDateTime; import java.util.List; +import static gaji.service.domain.common.entity.QSelectCategory.selectCategory; +import static gaji.service.domain.common.entity.QSelectHashtag.selectHashtag; import static gaji.service.domain.post.entity.QCommnuityPost.commnuityPost; import static gaji.service.domain.user.entity.QUser.user; @@ -30,7 +34,8 @@ public class CommunityPostQueryDslRepositoryImpl implements CommunityPostQueryDs private final CategoryService categoryService; @Override - public Slice findAllFetchJoinWithUser(Integer lastPopularityScore, + public Slice findAllFetchJoinWithUser(String keyword, + Integer lastPopularityScore, Long lastPostId, Integer lastLikeCnt, Integer lastHit, @@ -44,6 +49,10 @@ public Slice findAllFetchJoinWithUser(Integer lastPopularityScore List postList = jpaQueryFactory. selectFrom(commnuityPost) .leftJoin(commnuityPost.user, user) + .join(selectCategory) + .on(joinSelectCategory(commnuityPost.id, commnuityPost.type)) +// .join(selectHashtag) // TODO: 연관관계가 맺어져 있지 않아도 조인 가능, 추후 리팩토링 고려 +// .on(joinSelectHashtag(commnuityPost.id, commnuityPost.type)) .fetchJoin() .where( ltPopularityScore(lastPopularityScore), @@ -52,7 +61,8 @@ public Slice findAllFetchJoinWithUser(Integer lastPopularityScore ltHit(lastHit), postTypeEq(postType), postStatusEq(postStatus), - postIdIn(entityIdList) + postIdIn(entityIdList), + searchByKeyword(keyword) ) .orderBy(orderBySortType(sortType)) .limit(pageable.getPageSize() + 1) @@ -120,9 +130,18 @@ private BooleanExpression ltHit(Integer lastHit) { return (lastHit != null) ? commnuityPost.hit.lt(lastHit) : null; } - private BooleanExpression searchKeyword(String keyword) { - return (keyword != null) ? commnuityPost.title.containsIgnoreCase(keyword) - : null; + private BooleanExpression searchByKeyword(String keyword) { + return (keyword != null) ? commnuityPost.title.containsIgnoreCase(keyword).or(commnuityPost.body.containsIgnoreCase(keyword)) : null; + } + + private BooleanExpression joinSelectHashtag(NumberPath entityId, EnumPath postType) { + return selectHashtag.entityId.eq(entityId) + .and(selectHashtag.type.eq(postType)); + } + + private BooleanExpression joinSelectCategory(NumberPath entityId, EnumPath postType) { + return selectCategory.entityId.eq(entityId) + .and(selectCategory.type.eq(postType)); } private Slice checkLastPage(Pageable pageable, List postList) { diff --git a/src/main/java/gaji/service/domain/post/service/CommunityCommentService.java b/src/main/java/gaji/service/domain/post/service/CommunityCommentService.java index eba62850..3f357c77 100644 --- a/src/main/java/gaji/service/domain/post/service/CommunityCommentService.java +++ b/src/main/java/gaji/service/domain/post/service/CommunityCommentService.java @@ -2,7 +2,7 @@ import gaji.service.domain.post.entity.CommnuityPost; import gaji.service.domain.post.entity.CommunityComment; -import gaji.service.domain.post.web.dto.PostRequestDTO; +import gaji.service.domain.post.web.dto.CommunityPostRequestDTO; import gaji.service.domain.user.entity.User; import org.springframework.data.domain.Slice; @@ -10,10 +10,11 @@ public interface CommunityCommentService { CommunityComment saveNewComment(CommunityComment comment); - CommunityComment createCommentByCheckParentCommentIdIsNull(Long parentCommentId, PostRequestDTO.WriteCommentDTO request, User findUser, CommnuityPost findPost); + CommunityComment createCommentByCheckParentCommentIdIsNull(Long parentCommentId, CommunityPostRequestDTO.WriteCommentRequestDTO request, User findUser, CommnuityPost findPost); void hardDeleteComment(CommunityComment comment); CommunityComment findByCommentId(Long commentId); Slice getCommentListByPost(Long postId, Integer lastGroupNum, int page, int size); - void validCommentOwner(Long userId, CommunityComment comment); + boolean isCommentWriter(Long userId, CommunityComment comment); + void validCommentWriter(Long userId, CommunityComment comment); } diff --git a/src/main/java/gaji/service/domain/post/service/CommunityCommentServiceImpl.java b/src/main/java/gaji/service/domain/post/service/CommunityCommentServiceImpl.java index 4a5a2d91..9fe9c7c1 100644 --- a/src/main/java/gaji/service/domain/post/service/CommunityCommentServiceImpl.java +++ b/src/main/java/gaji/service/domain/post/service/CommunityCommentServiceImpl.java @@ -5,7 +5,7 @@ import gaji.service.domain.post.entity.CommnuityPost; import gaji.service.domain.post.entity.CommunityComment; import gaji.service.domain.post.repository.CommunityCommentJpaRepository; -import gaji.service.domain.post.web.dto.PostRequestDTO; +import gaji.service.domain.post.web.dto.CommunityPostRequestDTO; import gaji.service.domain.user.entity.User; import gaji.service.global.exception.RestApiException; import lombok.RequiredArgsConstructor; @@ -29,7 +29,7 @@ public CommunityComment saveNewComment(CommunityComment newComment) { @Override @Transactional - public CommunityComment createCommentByCheckParentCommentIdIsNull(Long parentCommentId, PostRequestDTO.WriteCommentDTO request, User findUser, CommnuityPost findPost) { + public CommunityComment createCommentByCheckParentCommentIdIsNull(Long parentCommentId, CommunityPostRequestDTO.WriteCommentRequestDTO request, User findUser, CommnuityPost findPost) { if (parentCommentId != null) { CommunityComment parentComment = findByCommentId(parentCommentId); return CommunityPostConverter.toComment(request, findUser, findPost, parentComment); @@ -58,7 +58,12 @@ public CommunityComment findByCommentId(Long commentId) { } @Override - public void validCommentOwner(Long userId, CommunityComment comment) { + public boolean isCommentWriter(Long userId, CommunityComment comment) { + return comment.getUser().getId().equals(userId); + } + + @Override + public void validCommentWriter(Long userId, CommunityComment comment) { if (!comment.getUser().getId().equals(userId)) { throw new RestApiException(CommunityCommentErrorStatus._NOT_AUTHORIZED); } diff --git a/src/main/java/gaji/service/domain/post/service/CommunityPostCommandService.java b/src/main/java/gaji/service/domain/post/service/CommunityPostCommandService.java index 5f653338..ea7ca245 100644 --- a/src/main/java/gaji/service/domain/post/service/CommunityPostCommandService.java +++ b/src/main/java/gaji/service/domain/post/service/CommunityPostCommandService.java @@ -4,12 +4,12 @@ import gaji.service.domain.post.entity.CommunityComment; import gaji.service.domain.post.entity.PostBookmark; import gaji.service.domain.post.entity.PostLikes; -import gaji.service.domain.post.web.dto.PostRequestDTO; +import gaji.service.domain.post.web.dto.CommunityPostRequestDTO; public interface CommunityPostCommandService { - CommnuityPost uploadPost(Long userId, PostRequestDTO.UploadPostDTO request); - CommunityComment writeCommentOnCommunityPost(Long userId, Long postId, Long parentCommentId, PostRequestDTO.WriteCommentDTO request); + CommnuityPost uploadPost(Long userId, CommunityPostRequestDTO.UploadPostRequestDTO request); + CommunityComment writeCommentOnCommunityPost(Long userId, Long postId, Long parentCommentId, CommunityPostRequestDTO.WriteCommentRequestDTO request); void hardDeleteComment(Long userId, Long commentId); void hardDeleteCommunityPost(Long userId, Long postId); PostBookmark bookmarkCommunityPost(Long userId, Long postId); diff --git a/src/main/java/gaji/service/domain/post/service/CommunityPostCommandServiceImpl.java b/src/main/java/gaji/service/domain/post/service/CommunityPostCommandServiceImpl.java index 1a1deb98..3b328d12 100644 --- a/src/main/java/gaji/service/domain/post/service/CommunityPostCommandServiceImpl.java +++ b/src/main/java/gaji/service/domain/post/service/CommunityPostCommandServiceImpl.java @@ -8,6 +8,7 @@ import gaji.service.domain.common.entity.SelectHashtag; import gaji.service.domain.common.service.CategoryService; import gaji.service.domain.common.service.HashtagService; +import gaji.service.domain.enums.CategoryEnum; import gaji.service.domain.post.converter.CommunityPostConverter; import gaji.service.domain.post.entity.CommnuityPost; import gaji.service.domain.post.entity.CommunityComment; @@ -16,7 +17,7 @@ import gaji.service.domain.post.repository.CommunityPostBookmarkRepository; import gaji.service.domain.post.repository.CommunityPostJpaRepository; import gaji.service.domain.post.repository.CommunityPostLikesRepository; -import gaji.service.domain.post.web.dto.PostRequestDTO; +import gaji.service.domain.post.web.dto.CommunityPostRequestDTO; import gaji.service.domain.user.entity.User; import gaji.service.domain.user.service.UserQueryService; import lombok.RequiredArgsConstructor; @@ -41,7 +42,7 @@ public class CommunityPostCommandServiceImpl implements CommunityPostCommandServ @Override - public CommnuityPost uploadPost(Long userId, PostRequestDTO.UploadPostDTO request) { + public CommnuityPost uploadPost(Long userId, CommunityPostRequestDTO.UploadPostRequestDTO request) { User findUser = userQueryService.findUserById(userId); CommnuityPost post = CommunityPostConverter.toPost(request, findUser); CommnuityPost newPost = communityPostJpaRepository.save(post); @@ -57,11 +58,12 @@ public CommnuityPost uploadPost(Long userId, PostRequestDTO.UploadPostDTO reques } // SelectCategory 저장 - if (request.getCategoryId() != null) { - Long categoryId = request.getCategoryId(); - Category findCateogry = categoryService.findByCategoryId(categoryId); + if (request.getCategory() != null) { + String categoryValue = request.getCategory(); + //Category findCategry = categoryService.findByCategoryId(categoryId); + Category findCategory = categoryService.findAllByCategory(CategoryEnum.fromValue(categoryValue)).get(0); - SelectCategory selectCategory = CategoryConverter.toSelectCategory(findCateogry, newPost.getId(), newPost.getType()); + SelectCategory selectCategory = CategoryConverter.toSelectCategory(findCategory, newPost.getId(), newPost.getType()); categoryService.saveSelectCategory(selectCategory); } @@ -69,7 +71,7 @@ public CommnuityPost uploadPost(Long userId, PostRequestDTO.UploadPostDTO reques } @Override - public CommunityComment writeCommentOnCommunityPost(Long userId, Long postId, Long parentCommentId, PostRequestDTO.WriteCommentDTO request) { + public CommunityComment writeCommentOnCommunityPost(Long userId, Long postId, Long parentCommentId, CommunityPostRequestDTO.WriteCommentRequestDTO request) { User findUser = userQueryService.findUserById(userId); CommnuityPost findPost = communityPostQueryService.findPostByPostId(postId); @@ -88,7 +90,7 @@ public void hardDeleteComment(Long userId, Long commentId) { CommunityComment findComment = communityCommentService.findByCommentId(commentId); // 검증 - communityCommentService.validCommentOwner(userId, findComment); + communityCommentService.validCommentWriter(userId, findComment); // 삭제 communityCommentService.hardDeleteComment(findComment); @@ -103,7 +105,7 @@ public void hardDeleteCommunityPost(Long userId, Long postId) { CommnuityPost findPost = communityPostQueryService.findPostByPostId(postId); // 검증 - communityPostQueryService.validPostOwner(userId, findPost); + communityPostQueryService.validPostWriter(userId, findPost); // 삭제 hashtagService.deleteAllByEntityIdAndType(findPost.getId(), findPost.getType()); @@ -132,7 +134,7 @@ public void cancelbookmarkCommunityPost(Long userId, Long postId) { CommnuityPost findPost = communityPostQueryService.findPostByPostId(postId); // 검증 - communityPostQueryService.validPostOwner(findUser.getId(), findPost); + communityPostQueryService.validPostWriter(findUser.getId(), findPost); // 삭제 postBookmarkRepository.deleteByUserAndPost(findUser, findPost); @@ -164,7 +166,7 @@ public void cancelLikeCommunityPost(Long userId, Long postId) { CommnuityPost findPost = communityPostQueryService.findPostByPostId(postId); // 검증 - communityPostQueryService.validPostOwner(findUser.getId(), findPost); + communityPostQueryService.validPostWriter(findUser.getId(), findPost); // 삭제 postLikesRepository.deleteByUserAndPost(findUser, findPost); diff --git a/src/main/java/gaji/service/domain/post/service/CommunityPostQueryService.java b/src/main/java/gaji/service/domain/post/service/CommunityPostQueryService.java index fb000603..3a06924c 100644 --- a/src/main/java/gaji/service/domain/post/service/CommunityPostQueryService.java +++ b/src/main/java/gaji/service/domain/post/service/CommunityPostQueryService.java @@ -10,19 +10,20 @@ public interface CommunityPostQueryService { CommnuityPost findPostByPostId(Long postId); - Slice getPostList(Integer lastPopularityScore, + Slice getPostList(String keyword, + Integer lastPopularityScore, Long lastPostId, Integer lastLikeCnt, Integer lastHit, PostTypeEnum postType, - Long categoryId, + String category, SortType sortType, PostStatusEnum filter, int page, int size); - Slice searchPostList(); CommnuityPost getPostDetail(Long postId); - void validPostOwner(Long userId, CommnuityPost post); + boolean isPostWriter(Long userId, CommnuityPost post); + void validPostWriter(Long userId, CommnuityPost post); void validExistsPostLikes(Long userId, CommnuityPost post); void validExistsPostBookmark(Long userId, CommnuityPost post); } diff --git a/src/main/java/gaji/service/domain/post/service/CommunityPostQueryServiceImpl.java b/src/main/java/gaji/service/domain/post/service/CommunityPostQueryServiceImpl.java index 275b48dd..149c5a39 100644 --- a/src/main/java/gaji/service/domain/post/service/CommunityPostQueryServiceImpl.java +++ b/src/main/java/gaji/service/domain/post/service/CommunityPostQueryServiceImpl.java @@ -1,5 +1,7 @@ package gaji.service.domain.post.service; +import gaji.service.domain.common.service.CategoryService; +import gaji.service.domain.enums.CategoryEnum; import gaji.service.domain.enums.PostStatusEnum; import gaji.service.domain.enums.PostTypeEnum; import gaji.service.domain.enums.SortType; @@ -23,19 +25,31 @@ public class CommunityPostQueryServiceImpl implements CommunityPostQueryService private final CommunityPostLikesRepository postLikesRepository; private final CommunityPostBookmarkRepository postBookmarkRepository; + private final CategoryService categoryService; + @Override - public Slice getPostList(Integer lastPopularityScore, + public Slice getPostList(String keyword, + Integer lastPopularityScore, Long lastPostId, Integer lastLikeCnt, Integer lastHit, PostTypeEnum postType, - Long categoryId, + String category, SortType sortType, PostStatusEnum postStatus, int page, int size) { PageRequest pageRequest = PageRequest.of(page, size); - return communityPostJpaRepository.findAllFetchJoinWithUser(lastPopularityScore, + + + Long categoryId=null; + if(category!=null) + { + categoryId=categoryService.findAllByCategory(CategoryEnum.fromValue(category)).get(0).getId(); + } + + return communityPostJpaRepository.findAllFetchJoinWithUser(keyword, + lastPopularityScore, lastPostId, lastLikeCnt, lastHit, @@ -46,11 +60,6 @@ public Slice getPostList(Integer lastPopularityScore, pageRequest); } - @Override - public Slice searchPostList() { - return null; - } - @Override public CommnuityPost getPostDetail(Long postId) { CommnuityPost findPost = communityPostJpaRepository.findByIdFetchJoinWithUser(postId); @@ -69,7 +78,12 @@ public CommnuityPost findPostByPostId(Long postId) { } @Override - public void validPostOwner(Long userId, CommnuityPost post) { + public boolean isPostWriter(Long userId, CommnuityPost post) { + return post.getUser().getId().equals(userId); + } + + @Override + public void validPostWriter(Long userId, CommnuityPost post) { if (!post.getUser().getId().equals(userId)) { throw new RestApiException(CommunityPostErrorStatus._NOT_AUTHORIZED); } diff --git a/src/main/java/gaji/service/domain/post/web/controller/CommunityPostRestController.java b/src/main/java/gaji/service/domain/post/web/controller/CommunityPostRestController.java index 42fb4b60..0d95980d 100644 --- a/src/main/java/gaji/service/domain/post/web/controller/CommunityPostRestController.java +++ b/src/main/java/gaji/service/domain/post/web/controller/CommunityPostRestController.java @@ -1,5 +1,7 @@ package gaji.service.domain.post.web.controller; +import gaji.service.domain.common.entity.SelectCategory; +import gaji.service.domain.common.service.CategoryService; import gaji.service.domain.enums.PostStatusEnum; import gaji.service.domain.enums.PostTypeEnum; import gaji.service.domain.enums.SortType; @@ -14,7 +16,7 @@ import gaji.service.domain.post.service.CommunityPostQueryService; import gaji.service.domain.post.web.dto.CommunityPostCommentResponseDTO; import gaji.service.domain.post.web.dto.CommunityPostResponseDTO; -import gaji.service.domain.post.web.dto.PostRequestDTO; +import gaji.service.domain.post.web.dto.CommunityPostRequestDTO; import gaji.service.global.base.BaseResponse; import gaji.service.jwt.service.TokenProviderService; import io.swagger.v3.oas.annotations.Operation; @@ -37,14 +39,17 @@ public class CommunityPostRestController { private final CommunityCommentService commentService; private final TokenProviderService tokenProviderService; private final CommunityPostConverter communityPostConverter; + private final CommunityCommentConverter communityCommentConverter; + + private final CategoryService categoryService; @PostMapping @Operation(summary = "커뮤니티 게시글 업로드 API", description = "커뮤니티의 게시글을 업로드하는 API입니다. 게시글 유형과 제목, 본문 내용을 검증합니다.") - public BaseResponse uploadPost(@RequestHeader("Authorization") String authorizationHeader, - @RequestBody @Valid PostRequestDTO.UploadPostDTO request) { + public BaseResponse uploadPost(@RequestHeader("Authorization") String authorizationHeader, + @RequestBody @Valid CommunityPostRequestDTO.UploadPostRequestDTO request) { Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); CommnuityPost newPost = communityPostCommandService.uploadPost(userId, request); - return BaseResponse.onSuccess(CommunityPostConverter.toUploadPostDTO(newPost)); + return BaseResponse.onSuccess(CommunityPostConverter.toUploadPostResponseDTO(newPost)); } @DeleteMapping("/{postId}") @@ -68,54 +73,51 @@ public BaseResponse getPostDetail(@Min(v @RequestHeader(value = "Authorization", required = false) String authorizationHeader) { Long userId = (authorizationHeader == null) ? null : tokenProviderService.getUserIdFromToken(authorizationHeader); CommnuityPost post = communityPostQueryService.getPostDetail(postId); - return BaseResponse.onSuccess(communityPostConverter.toPostDetailDTO(post, userId)); + SelectCategory category = categoryService.findByEntityIdAndType(post.getId(), post.getType()); + return BaseResponse.onSuccess(communityPostConverter.toPostDetailDTO(post, userId, category)); } @GetMapping("/preivew") - @Operation(summary = "커뮤니티 게시글 미리보기 목록 조회 API", description = "아직은 무한스크롤로 구현되어있지 않고, 모든 목록을 조회합니다.") + @Operation(summary = "커뮤니티 게시글 미리보기 목록 조회 API", description = "hot 게시글, 커뮤니티 게시글 미리보기 목록, 검색 API에 모두 사용 가능합니다.") @Parameters({ @Parameter(name = "lastPopularityScore", description = "마지막으로 조회한 게시글의 인기 점수"), @Parameter(name = "lastPostId", description = "마지막으로 조회한 게시글의 id"), @Parameter(name = "lastLikeCnt", description = "마지막으로 조회한 게시글의 좋아요 수"), @Parameter(name = "lastHit", description = "마지막으로 조회한 게시글의 조회수"), @Parameter(name = "postType", description = "게시글의 유형(블로그, 프로젝트, 질문)"), - @Parameter(name = "categoryId", description = "카테고리(DEVELOP, AI, HW, ... 정책 공통 사항 참조)의 id"), + @Parameter(name = "category", description = "카테고리"), @Parameter(name = "sortType", description = "정렬 유형(hot, recent, like, hit)"), @Parameter(name = "filter", description = "게시글의 상태(모집중, 모집완료, 미완료질문, 해결완료)"), }) - public BaseResponse getPostPreivewList(@Min(value = 0, message = "lastPopularityScore는 0 이상 이어야 합니다.") @RequestParam(required = false) Integer lastPopularityScore, + public BaseResponse getPostPreivewList(@RequestParam(required = false) String keyword, + @Min(value = 0, message = "lastPopularityScore는 0 이상 이어야 합니다.") @RequestParam(required = false) Integer lastPopularityScore, @Min(value = 1, message = "lastPostId는 1 이상 이어야 합니다.") @RequestParam(required = false) Long lastPostId, @Min(value = 0, message = "lastLikeCnt는 0 이상 이어야 합니다.") @RequestParam(required = false) Integer lastLikeCnt, @Min(value = 0, message = "lastHit은 0 이상 이어야 합니다.") @RequestParam(required = false) Integer lastHit, @RequestParam(required = false) PostTypeEnum postType, - @RequestParam(required = false) Long categoryId, + @RequestParam(required = false) String category, @RequestParam(required = false, defaultValue = "recent") SortType sortType, @RequestParam(required = false) PostStatusEnum filter, @Min(value = 0, message = "page는 0 이상 이어야 합니다.") @RequestParam(defaultValue = "0") int page, @Min(value = 1, message = "size는 1 이상 이어야 합니다.") @RequestParam(defaultValue = "10") int size) { - Slice postSlice = communityPostQueryService.getPostList(lastPopularityScore, lastPostId, lastLikeCnt, lastHit, postType, categoryId, sortType, filter, page, size); + Slice postSlice = communityPostQueryService.getPostList(keyword, lastPopularityScore, lastPostId, lastLikeCnt, lastHit, postType, category, sortType, filter, page, size); return BaseResponse.onSuccess(communityPostConverter.toPostPreviewListDTO(postSlice.getContent(), postSlice.hasNext())); } - @GetMapping("/search") - public BaseResponse searchCommunityPostList() { - return null; - } - @PostMapping("/{postId}/comments") @Operation(summary = "커뮤니티 게시글 댓글 작성 API", description = "커뮤니티의 게시글에 댓글을 작성하는 API입니다. 대댓글을 작성하는 거라면 Long 타입의 parentCommentId를 query parameter로 보내주시면 됩니다!") @Parameters({ @Parameter(name = "postId", description = "게시글 id"), @Parameter(name = "parentCommentId", description = "부모 댓글의 id, 대댓글 작성할 때 필요한 부모 댓글의 id입니다."), }) - public BaseResponse writeCommentOnCommunityPost(@RequestHeader("Authorization") String authorizationHeader, - @Min(value = 1, message = "postId는 1 이상 이어야 합니다.") @PathVariable Long postId, - @Min(value = 1, message = "parentCommentId는 1 이상 이어야 합니다.") @RequestParam(required = false) Long parentCommentId, - @RequestBody @Valid PostRequestDTO.WriteCommentDTO request) { + public BaseResponse writeCommentOnCommunityPost(@RequestHeader("Authorization") String authorizationHeader, + @Min(value = 1, message = "postId는 1 이상 이어야 합니다.") @PathVariable Long postId, + @Min(value = 1, message = "parentCommentId는 1 이상 이어야 합니다.") @RequestParam(required = false) Long parentCommentId, + @RequestBody @Valid CommunityPostRequestDTO.WriteCommentRequestDTO request) { Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); CommunityComment newComment = communityPostCommandService.writeCommentOnCommunityPost(userId, postId, parentCommentId, request); - return BaseResponse.onSuccess(CommunityCommentConverter.toWriteCommentDTO(newComment)); + return BaseResponse.onSuccess(CommunityCommentConverter.toWriteCommentResponseDTO(newComment)); } @DeleteMapping("/comments/{commentId}") @@ -132,13 +134,15 @@ public BaseResponse hardDeleteComment(@RequestHeader("Authorization") String aut @GetMapping("/{postId}/comments") @Operation(summary = "커뮤니티 게시글 댓글 목록 조회 API", description = "lastGroupNum에 마지막으로 조회한 댓글의 grounNum과, size로 조회할 데이터의 개수를 보내주세요.") - public BaseResponse getCommentList(@Min(value = 1, message = "postId는 1 이상 이어야 합니다.") @PathVariable Long postId, + public BaseResponse getCommentList(@RequestHeader(value = "Authorization", required = false) String authorizationHeader, + @Min(value = 1, message = "postId는 1 이상 이어야 합니다.") @PathVariable Long postId, @Min(value = 0, message = "lastGroupNum은 0 이상 이어야 합니다.") @RequestParam(required = false) Integer lastGroupNum, // 마지막 댓글 ID @Min(value = 0, message = "page는 0 이상 이어야 합니다.") @RequestParam(defaultValue = "0") int page, @Min(value = 1, message = "size는 1 이상 이어야 합니다.") @RequestParam(defaultValue = "10") int size) // 페이지 크기 (기본값 10)) { + Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); Slice commentSlice = commentService.getCommentListByPost(postId, lastGroupNum, page, size); - CommunityPostCommentResponseDTO.PostCommentListDTO postCommentDTOList = CommunityCommentConverter.toPostCommentListDTO(commentSlice.getContent(), commentSlice.hasNext()); + CommunityPostCommentResponseDTO.PostCommentListDTO postCommentDTOList = communityCommentConverter.toPostCommentListDTO(commentSlice.getContent(), commentSlice.hasNext(), userId); return BaseResponse.onSuccess(postCommentDTOList); } diff --git a/src/main/java/gaji/service/domain/post/web/dto/CommunityPostCommentResponseDTO.java b/src/main/java/gaji/service/domain/post/web/dto/CommunityPostCommentResponseDTO.java index 330cb567..890026f3 100644 --- a/src/main/java/gaji/service/domain/post/web/dto/CommunityPostCommentResponseDTO.java +++ b/src/main/java/gaji/service/domain/post/web/dto/CommunityPostCommentResponseDTO.java @@ -14,7 +14,7 @@ public class CommunityPostCommentResponseDTO { @Getter @NoArgsConstructor @AllArgsConstructor - public static class WriteCommentDTO { + public static class WriteCommentResponseDTO { private Long commentId; } @@ -30,6 +30,7 @@ public static class PostCommentDTO { private Integer groupNum; private int depth; private String createdAt; + private boolean isWriter; } @Builder diff --git a/src/main/java/gaji/service/domain/post/web/dto/PostRequestDTO.java b/src/main/java/gaji/service/domain/post/web/dto/CommunityPostRequestDTO.java similarity index 80% rename from src/main/java/gaji/service/domain/post/web/dto/PostRequestDTO.java rename to src/main/java/gaji/service/domain/post/web/dto/CommunityPostRequestDTO.java index ef938b35..7322498c 100644 --- a/src/main/java/gaji/service/domain/post/web/dto/PostRequestDTO.java +++ b/src/main/java/gaji/service/domain/post/web/dto/CommunityPostRequestDTO.java @@ -2,6 +2,7 @@ import gaji.service.domain.common.annotation.CheckHashtagBlank; import gaji.service.domain.common.annotation.CheckHashtagLength; +import gaji.service.domain.common.annotation.ExistsCategory; import gaji.service.domain.enums.PostTypeEnum; import gaji.service.domain.post.annotation.ExistPostType; import io.swagger.v3.oas.annotations.media.Schema; @@ -12,12 +13,12 @@ import java.util.ArrayList; import java.util.List; -public class PostRequestDTO { +public class CommunityPostRequestDTO { @Schema(description = "커뮤니티 게시글 저장 DTO") @Getter @RequiredArgsConstructor - public static class UploadPostDTO { + public static class UploadPostRequestDTO { @Schema(description = "게시글 제목") @NotBlank(message = "게시글 제목을 입력해주세요.") private final String title; @@ -26,7 +27,7 @@ public static class UploadPostDTO { @NotBlank(message = "게시글 본문을 입력해주세요.") private final String body; - @Schema(description = "게시글 썸네일 Url(없으면 첫번째 사진으로 설정)") + @Schema(description = "게시글 썸네일 Url") private final String thumbnailUrl; @Schema(description = "게시글 유형(프로젝트 모집, 질문, 블로그)") @@ -38,15 +39,15 @@ public static class UploadPostDTO { @CheckHashtagLength private final List hashtagList = new ArrayList<>(); - @Schema(description = "카테고리의 id") - // TODO: 카테고리 존재 여부 검증 애노테이션 적용 - private final Long categoryId; + @Schema(description = "카테고리") + @ExistsCategory + private final String category; } @Schema(description = "커뮤니티 게시글 댓글 작성 DTO") @Getter @RequiredArgsConstructor - public static class WriteCommentDTO { + public static class WriteCommentRequestDTO { @Schema(description = "댓글 본문") @NotBlank(message = "댓글 본문을 입력해주세요.") private String body; diff --git a/src/main/java/gaji/service/domain/post/web/dto/CommunityPostResponseDTO.java b/src/main/java/gaji/service/domain/post/web/dto/CommunityPostResponseDTO.java index 11db2441..f0d4e3c5 100644 --- a/src/main/java/gaji/service/domain/post/web/dto/CommunityPostResponseDTO.java +++ b/src/main/java/gaji/service/domain/post/web/dto/CommunityPostResponseDTO.java @@ -1,6 +1,8 @@ package gaji.service.domain.post.web.dto; import gaji.service.domain.common.web.dto.HashtagResponseDTO; +import gaji.service.domain.enums.CategoryEnum; +import gaji.service.domain.enums.PostStatusEnum; import gaji.service.domain.enums.PostTypeEnum; import lombok.AllArgsConstructor; import lombok.Builder; @@ -17,7 +19,7 @@ public class CommunityPostResponseDTO { @Getter @NoArgsConstructor @AllArgsConstructor - public static class UploadPostDTO { + public static class UploadPostResponseDTO { Long postId; } @@ -52,6 +54,7 @@ public static class PostPreviewDTO { private String uploadTime; private int hit; private int popularityScore; + private PostStatusEnum status; private List hashtagList = new ArrayList<>(); } @@ -73,12 +76,17 @@ public static class PostDetailDTO { private PostTypeEnum type; private String createdAt; private int hit; + private int likeCnt; + private int bookmarkCnt; private int commentCnt; private String userNickname; private String title; - private boolean isBookMarked; - private boolean isLiked; + private boolean isWriter; // 게시글을 조회한 사람이 작성자가 맞는지 + private boolean bookMarkStatus; + private boolean likeStatus; private String body; + private String category; + private PostStatusEnum status; private List hashtagList = new ArrayList<>(); } diff --git a/src/main/java/gaji/service/domain/recruit/converter/RecruitConverter.java b/src/main/java/gaji/service/domain/recruit/converter/RecruitConverter.java index 8729e132..9ef5031a 100644 --- a/src/main/java/gaji/service/domain/recruit/converter/RecruitConverter.java +++ b/src/main/java/gaji/service/domain/recruit/converter/RecruitConverter.java @@ -5,13 +5,14 @@ import gaji.service.domain.enums.Role; import gaji.service.domain.recruit.entity.RecruitPostBookmark; import gaji.service.domain.recruit.entity.RecruitPostLikes; +import gaji.service.domain.studyMate.entity.StudyApplicant; +import gaji.service.domain.user.entity.User; import gaji.service.domain.recruit.entity.StudyComment; import gaji.service.domain.recruit.web.dto.RecruitRequestDTO; import gaji.service.domain.recruit.web.dto.RecruitResponseDTO; import gaji.service.domain.room.entity.Material; import gaji.service.domain.room.entity.Room; import gaji.service.domain.studyMate.entity.StudyMate; -import gaji.service.domain.user.entity.User; import gaji.service.global.converter.DateConverter; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Component; @@ -58,15 +59,15 @@ public static Material toMaterial(String materialPath, Room room) { .build(); } - public static StudyMate toStudyMate(User user, Room room) { + public static StudyMate toStudyMate(User user, Room room, Role role) { return StudyMate.builder() .user(user) .room(room) - .role(Role.READER) + .role(role) .build(); } - public static RecruitResponseDTO.studyDetailResponseDTO toStudyDetailDTO(User user, Room room, CategoryEnum category) { + public static RecruitResponseDTO.studyDetailResponseDTO toStudyDetailDTO(User user, Room room, CategoryEnum category, boolean likeStatus, boolean bookmarkStatus) { return RecruitResponseDTO.studyDetailResponseDTO.builder() .userNickName(user.getNickname()) .userActive(user.getStatus()) @@ -78,8 +79,10 @@ public static RecruitResponseDTO.studyDetailResponseDTO toStudyDetailDTO(User us .recruitPostTypeEnum(room.getRecruitPostTypeEnum()) .studyCategory(category) .views(room.getViews()) - .likes(room.getLikes()) - .bookmarks(room.getBookmarks()) + .likeCnt(room.getLikes()) + .bookmarkCnt(room.getBookmarks()) + .likeStatus(likeStatus) + .bookmarkStatus(bookmarkStatus) .recruitStartTime(room.getRecruitStartDay()) .recruitEndTime(room.getRecruitEndDay()) @@ -161,7 +164,7 @@ public static RecruitResponseDTO.PreviewResponseDTO toPreviewDTO(Room room) { .recruitStatus(room.getRecruitPostTypeEnum()) .applicant(room.getStudyApplicantList().size()) .name(room.getName()) - .deadLine(ChronoUnit.DAYS.between(room.getRecruitEndDay(), LocalDate.now())) + .deadLine(ChronoUnit.DAYS.between(LocalDate.now(), room.getRecruitEndDay())) .description(room.getDescription()) .createdAt(DateConverter.convertToRelativeTimeFormat(room.getCreatedAt())) .recruitMaxCount(room.getPeopleMaximum()) @@ -171,4 +174,10 @@ public static RecruitResponseDTO.PreviewResponseDTO toPreviewDTO(Room room) { public static List toPreviewDTOLIST(List roomList) { return roomList.stream().map(RecruitConverter::toPreviewDTO).collect(Collectors.toList()); } + + public static RecruitResponseDTO.JoinStudyResponseDTO toJoinStudyResponseDTO(Long roomId) { + return RecruitResponseDTO.JoinStudyResponseDTO.builder() + .roomId(roomId) + .build(); + } } diff --git a/src/main/java/gaji/service/domain/recruit/service/RecruitCommandService.java b/src/main/java/gaji/service/domain/recruit/service/RecruitCommandService.java index 965390e6..241b50bf 100644 --- a/src/main/java/gaji/service/domain/recruit/service/RecruitCommandService.java +++ b/src/main/java/gaji/service/domain/recruit/service/RecruitCommandService.java @@ -2,6 +2,8 @@ import gaji.service.domain.recruit.web.dto.RecruitRequestDTO; import gaji.service.domain.recruit.web.dto.RecruitResponseDTO; +import gaji.service.domain.room.entity.Room; +import gaji.service.domain.user.entity.User; public interface RecruitCommandService { @@ -14,4 +16,13 @@ public interface RecruitCommandService { RecruitResponseDTO.StudyBookmarkIdDTO bookmarkStudy(Long userId, Long roomId); void unBookmarkStudy(Long userId, Long roomId); + + RecruitResponseDTO.JoinStudyResponseDTO joinStudy(Long userId, Long roomId); + + void leaveStudy(Long userId, Long roomId); + + void kickStudy(Long userId, Long roomId, Long targetId); + + boolean userLikeStatus(Room room, User user); + boolean userBookmarkStatus(Room room, User user); } diff --git a/src/main/java/gaji/service/domain/recruit/service/RecruitCommandServiceImpl.java b/src/main/java/gaji/service/domain/recruit/service/RecruitCommandServiceImpl.java index d7513ea9..c23da98e 100644 --- a/src/main/java/gaji/service/domain/recruit/service/RecruitCommandServiceImpl.java +++ b/src/main/java/gaji/service/domain/recruit/service/RecruitCommandServiceImpl.java @@ -1,9 +1,12 @@ package gaji.service.domain.recruit.service; +import gaji.service.domain.common.converter.CategoryConverter; import gaji.service.domain.common.entity.Category; import gaji.service.domain.common.entity.SelectCategory; import gaji.service.domain.common.service.CategoryService; +import gaji.service.domain.enums.CategoryEnum; import gaji.service.domain.enums.PostTypeEnum; +import gaji.service.domain.enums.Role; import gaji.service.domain.recruit.code.RecruitErrorStatus; import gaji.service.domain.recruit.converter.RecruitConverter; import gaji.service.domain.recruit.entity.RecruitPostBookmark; @@ -17,11 +20,14 @@ import gaji.service.domain.room.service.MaterialCommandService; import gaji.service.domain.room.service.RoomCommandService; import gaji.service.domain.room.service.RoomQueryService; +import gaji.service.domain.studyMate.code.StudyMateErrorStatus; import gaji.service.domain.studyMate.entity.StudyMate; -import gaji.service.domain.studyMate.repository.StudyMateRepository; +import gaji.service.domain.studyMate.service.StudyMateCommandService; +import gaji.service.domain.studyMate.service.StudyMateQueryService; import gaji.service.domain.user.entity.User; import gaji.service.domain.user.service.UserQueryService; import gaji.service.global.exception.RestApiException; +import gaji.service.global.exception.code.status.GlobalErrorStatus; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -37,7 +43,8 @@ public class RecruitCommandServiceImpl implements RecruitCommandService { private final UserQueryService userQueryService; private final CategoryService categoryService; private final RoomQueryService roomQueryService; - private final StudyMateRepository studyMateRepository; + private final StudyMateCommandService studyMateCommandService; + private final StudyMateQueryService studyMateQueryService; private final MaterialCommandService materialCommandService; private final RecruitPostLikesRepository recruitPostLikesRepository; private final RecruitPostBookmarkRepository recruitPostBookmarkRepository; @@ -62,8 +69,8 @@ public RecruitResponseDTO.CreateRoomResponseDTO createRoom(RecruitRequestDTO.Cre User user = userQueryService.findUserById(userId); Room room = RecruitConverter.toRoom(request, user, request.getThumbnailUrl(), inviteCode, peopleMaximum); - StudyMate studyMate = RecruitConverter.toStudyMate(user, room); - studyMateRepository.save(studyMate); + StudyMate studyMate = RecruitConverter.toStudyMate(user, room, Role.READER); + studyMateCommandService.saveStudyMate(studyMate); if (request.getMaterialList() != null && !request.getMaterialList().isEmpty()){ Material material; @@ -76,16 +83,15 @@ public RecruitResponseDTO.CreateRoomResponseDTO createRoom(RecruitRequestDTO.Cre roomCommandService.saveRoom(room); - Category category = Category.builder() - .category(request.getCategory()) - .build(); - categoryService.saveCategory(category); + // if (request.getCategoryId() == null) { + // throw new RestApiException(GlobalErrorStatus._INVALID_CATEGORY); + // } - SelectCategory selectCategory = SelectCategory.builder() - .category(category) - .entityId(room.getId()) - .type(PostTypeEnum.ROOM) - .build(); + //Long categoryId = request.getCategoryId(); + //Category category = categoryService.findByCategoryId(categoryId); + Category category = categoryService.findAllByCategory(CategoryEnum.fromValue(request.getCategory())).get(0); + + SelectCategory selectCategory = CategoryConverter.toSelectCategory(category, room.getId(), PostTypeEnum.ROOM); categoryService.saveSelectCategory(selectCategory); return RecruitConverter.toResponseDTO(room); @@ -163,4 +169,67 @@ public void unBookmarkStudy(Long userId, Long roomId) { recruitPostBookmarkRepository.deleteByUserAndRoom(user, room); room.decreaseBookmark(); } + + @Override + @Transactional + public RecruitResponseDTO.JoinStudyResponseDTO joinStudy(Long userId, Long roomId) { + User user = userQueryService.findUserById(userId); + Room room = roomQueryService.findRoomById(roomId); + + if (studyMateQueryService.existsByUserAndRoom(user, room)) { + throw new RestApiException(StudyMateErrorStatus._USER_ALREADY_JOIN); + } + + StudyMate studyMate = RecruitConverter.toStudyMate(user, room, Role.MEMBER); + studyMateCommandService.saveStudyMate(studyMate); + + return RecruitConverter.toJoinStudyResponseDTO(roomId); + } + + @Override + @Transactional + public void leaveStudy(Long userId, Long roomId) { + User user = userQueryService.findUserById(userId); + Room room = roomQueryService.findRoomById(roomId); + + if (!studyMateQueryService.existsByUserAndRoom(user, room)) { + throw new RestApiException(StudyMateErrorStatus._USER_NOT_IN_STUDYROOM); + } else { + if (studyMateQueryService.checkLeader(user, room)) { + throw new RestApiException(StudyMateErrorStatus._LEADER_IMPOSSIBLE_LEAVE); + } + studyMateCommandService.deleteByUserAndRoom(user, room); + } + } + + @Override + @Transactional + public void kickStudy(Long userId, Long roomId, Long targetId) { + Room room = roomQueryService.findRoomById(roomId); + User target = userQueryService.findUserById(targetId); + + StudyMate studyMate = studyMateQueryService.findByUserIdAndRoomId(userId, roomId); + if (studyMate.getRole() != Role.READER) { + throw new RestApiException(StudyMateErrorStatus._ONLY_LEADER_POSSIBLE); + } + + if (!studyMateQueryService.existsByUserAndRoom(target, room)) { + throw new RestApiException(StudyMateErrorStatus._USER_NOT_IN_STUDYROOM); + } else { + if (studyMateQueryService.checkLeader(target, room)) { + throw new RestApiException(StudyMateErrorStatus._LEADER_IMPOSSIBLE_LEAVE); + } + studyMateCommandService.deleteByUserAndRoom(target, room); + } + } + + @Override + public boolean userLikeStatus(Room room, User user) { + return recruitPostLikesRepository.existsByUserAndRoom(user, room); + } + + @Override + public boolean userBookmarkStatus(Room room, User user){ + return recruitPostBookmarkRepository.existsByUserAndRoom(user, room); + } } diff --git a/src/main/java/gaji/service/domain/recruit/service/RecruitQueryService.java b/src/main/java/gaji/service/domain/recruit/service/RecruitQueryService.java index 85a4518f..22fa6923 100644 --- a/src/main/java/gaji/service/domain/recruit/service/RecruitQueryService.java +++ b/src/main/java/gaji/service/domain/recruit/service/RecruitQueryService.java @@ -9,7 +9,7 @@ public interface RecruitQueryService { RecruitResponseDTO.studyDetailResponseDTO getStudyDetail(Long roomId); RecruitResponseDTO.PreviewListResponseDTO getPreviewList( - CategoryEnum category, PreviewFilter filter, SortType sort, String query, Long value, int pageSize); + String category, PreviewFilter filter, SortType sort, String query, Long value, int pageSize); - RecruitResponseDTO.DefaultPreviewListResponseDTO getDefaultPreview(boolean isFirst, Integer nextCategoryIndex, int pageSize); + RecruitResponseDTO.DefaultPreviewListResponseDTO getDefaultPreview(boolean isFirst, int nextCategoryId, int pageSize); } diff --git a/src/main/java/gaji/service/domain/recruit/service/RecruitQueryServiceImpl.java b/src/main/java/gaji/service/domain/recruit/service/RecruitQueryServiceImpl.java index 4e19bdcd..140539db 100644 --- a/src/main/java/gaji/service/domain/recruit/service/RecruitQueryServiceImpl.java +++ b/src/main/java/gaji/service/domain/recruit/service/RecruitQueryServiceImpl.java @@ -2,6 +2,7 @@ import gaji.service.domain.common.entity.SelectCategory; import gaji.service.domain.common.service.CategoryService; +import gaji.service.domain.common.web.dto.CategoryResponseDTO; import gaji.service.domain.enums.CategoryEnum; import gaji.service.domain.enums.PostTypeEnum; import gaji.service.domain.enums.PreviewFilter; @@ -23,7 +24,6 @@ import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; @Service @@ -35,6 +35,7 @@ public class RecruitQueryServiceImpl implements RecruitQueryService { private final RoomCommandService roomCommandService; private final CategoryService categoryService; private final RecruitRepository recruitRepository; + private final RecruitCommandService recruitCommandService; @Override @Transactional @@ -42,6 +43,8 @@ public RecruitResponseDTO.studyDetailResponseDTO getStudyDetail(Long roomId) { Room room = roomQueryService.findRoomById(roomId); User user = userQueryService.findUserById(room.getUser().getId()); + boolean likeStatus = recruitCommandService.userLikeStatus(room, user); + boolean bookmarkStatus = recruitCommandService.userBookmarkStatus(room, user); room.addView(); roomCommandService.saveRoom(room); @@ -51,13 +54,23 @@ public RecruitResponseDTO.studyDetailResponseDTO getStudyDetail(Long roomId) { CategoryEnum category = RecruitConverter.toCategory(selectCategory); - return RecruitConverter.toStudyDetailDTO(user, room, category); + return RecruitConverter.toStudyDetailDTO(user, room, category, likeStatus, bookmarkStatus); } @Override @Transactional(readOnly = true) public RecruitResponseDTO.PreviewListResponseDTO getPreviewList( - CategoryEnum category, PreviewFilter filter, SortType sort, String query, Long value, int pageSize) { + String categoryValue, PreviewFilter filter, SortType sort, String query, Long value, int pageSize) { + + //CategoryEnum category = null; + //if (categoryid != null) { + // category = categoryService.findByCategoryId(categoryId).getCategory(); + //} + CategoryEnum category = null; + if (categoryValue != null) { + category = CategoryEnum.fromValue(categoryValue); + } + validateQuery(query); @@ -67,22 +80,30 @@ public RecruitResponseDTO.PreviewListResponseDTO getPreviewList( } @Override - public RecruitResponseDTO.DefaultPreviewListResponseDTO getDefaultPreview(boolean isFirst, Integer nextCategoryIndex, int pageSize) { + public RecruitResponseDTO.DefaultPreviewListResponseDTO getDefaultPreview(boolean isFirst, int nextCategoryId, int pageSize) { Pageable pageable = PageRequest.of(0, pageSize); List defaultPreviewList = new ArrayList<>(); + List categoryList = categoryService.findAllCategory(); - List categoryList = new ArrayList<>(Arrays.asList(CategoryEnum.values())); - int count; + boolean hasNext = true; + int count = 0; + int getPreviewCount; if (isFirst) { - nextCategoryIndex = 0; - count = 4; + nextCategoryId = 0; + getPreviewCount = 4; } else { - count = nextCategoryIndex + 1; + nextCategoryId--; + getPreviewCount = 1; } - for (int i = nextCategoryIndex; i < count; i++) { - CategoryEnum category = categoryList.get(i); + while (count < getPreviewCount) { + if (categoryList.size() <= nextCategoryId) { + hasNext = false; + break; + } + + CategoryEnum category = categoryList.get(nextCategoryId++).getCategory(); RecruitResponseDTO.DefaultPreviewDTO previewList = recruitRepository.findByCategory(category, pageable); @@ -90,13 +111,25 @@ public RecruitResponseDTO.DefaultPreviewListResponseDTO getDefaultPreview(boolea if (previewList.getPreviewList() == null || previewList.getPreviewList().isEmpty()) { continue; } - + count++; defaultPreviewList.add(previewList); } + if (hasNext) { + RecruitResponseDTO.DefaultPreviewDTO previewList = + recruitRepository.findByCategory(categoryList.get(nextCategoryId).getCategory(), PageRequest.of(0, 1)); + + if (previewList.getPreviewList().isEmpty()) { + hasNext = false; + nextCategoryId = -1; + } + } + + return RecruitResponseDTO.DefaultPreviewListResponseDTO.builder() .defaultPreviewList(defaultPreviewList) - .nextIndex(count) + .nextCategoryId(++nextCategoryId) + .hasNext(hasNext) .build(); } diff --git a/src/main/java/gaji/service/domain/recruit/web/controller/RecruitController.java b/src/main/java/gaji/service/domain/recruit/web/controller/RecruitController.java index 66c9f4fb..a94bb889 100644 --- a/src/main/java/gaji/service/domain/recruit/web/controller/RecruitController.java +++ b/src/main/java/gaji/service/domain/recruit/web/controller/RecruitController.java @@ -29,7 +29,7 @@ public class RecruitController { private final StudyCommentQueryService studyCommentQueryService; @PostMapping("") - @Operation(summary = "스터디 모집 게시글 생성 성성 API", description = "스터디 모집 게시글을 생성하는 API입니다.") + @Operation(summary = "스터디 모집 게시글 생성 API", description = "스터디 모집 게시글을 생성하는 API입니다.") public BaseResponse createRoom( @RequestBody @Valid RecruitRequestDTO.CreateRoomDTO request, @RequestHeader("Authorization") String authorizationHeader) { @@ -96,7 +96,7 @@ public BaseResponse likeStudy( @Operation(summary = "스터디 모집 게시글 좋아요 취소 API", description = "스터디 모집 게시글 좋아요 취소하는 API 입니다.") public BaseResponse unLikeStudy( @RequestHeader("Authorization") String authorizationHeader, - @PathVariable @Min(value = 1, message = "roomId는 1 이상 이어야 합니다.") Long roomId) { + @PathVariable @Min(value = 1, message = "roomId는 1 이상 이어야 합니다.") Long roomId) { Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); recruitCommandService.unLikeStudy(userId, roomId); return BaseResponse.onSuccess(null); @@ -116,7 +116,7 @@ public BaseResponse bookmarkStudy( @Operation(summary = "스터디 모집 게시글 북마크 취소 API", description = "스터디 모집 게시글 북마크 취소하는 API 입니다.") public BaseResponse unBookmarkStudy( @RequestHeader("Authorization") String authorizationHeader, - @PathVariable @Min(value = 1, message = "roomId는 1 이상 이어야 합니다.") Long roomId) { + @PathVariable @Min(value = 1, message = "roomId는 1 이상 이어야 합니다.") Long roomId) { Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); recruitCommandService.unBookmarkStudy(userId, roomId); return BaseResponse.onSuccess(null); @@ -125,12 +125,12 @@ public BaseResponse unBookmarkStudy( @GetMapping("/preview") @Operation(summary = "스터디 모집 게시글 미리보기 목록 조회 API", description = "모집 게시글 목록을 조회하는 API 입니다.") public BaseResponse getPreviewList( - @RequestParam(required = false) CategoryEnum category, + @RequestParam(required = false) String category, @RequestParam(required = false) PreviewFilter filter, @RequestParam(defaultValue = "recent") SortType sort, @RequestParam(required = false) String query, @RequestParam(required = false) @Min(value = 0, message = "lastValue는 0 이상 입니다.") Long lastValue, - @RequestParam(value = "page", defaultValue = "20") @Min(value = 1, message = "pageSize는 0보다 커야 합니다.") int pageSize){ + @RequestParam(value = "page", defaultValue = "20") @Min(value = 1, message = "pageSize는 0보다 커야 합니다.") int pageSize) { RecruitResponseDTO.PreviewListResponseDTO responseDTO = recruitQueryService.getPreviewList(category, filter, sort, query, lastValue, pageSize); return BaseResponse.onSuccess(responseDTO); @@ -139,12 +139,44 @@ public BaseResponse getPreviewList( @GetMapping("/preview-default") @Operation(summary = "스터디 미리보기 목록 조회 기본 페이지 API", description = "스터디 목록 조회 기본 페이지입니다.") public BaseResponse getDefaultPreviewList( - @RequestParam(defaultValue = "0") @Min(value = 0, message = "index는 0 이상 이어야 합니다.") Integer nextCategoryIndex, + @RequestParam(defaultValue = "0") @Min(value = 1, message = "nextCategoryId는 1이상 이어야 합니다.") int nextCategoryId, @RequestParam(defaultValue = "true") boolean isFirst, @RequestParam(value = "page", defaultValue = "5") @Min(value = 1, message = "pageSize는 0보다 커야 합니다.") int pageSize) { - RecruitResponseDTO.DefaultPreviewListResponseDTO responseDTO = recruitQueryService.getDefaultPreview(isFirst, nextCategoryIndex, pageSize); + RecruitResponseDTO.DefaultPreviewListResponseDTO responseDTO = recruitQueryService.getDefaultPreview(isFirst, nextCategoryId, pageSize); return BaseResponse.onSuccess(responseDTO); } + + @PostMapping("/{roomId}") + @Operation(summary = "스터디 가지기 API", description = "스터디에 참여하는 API 입니다.") + public BaseResponse joinStudy( + @RequestHeader("Authorization") String authorizationHeader, + @PathVariable @Min(value = 1, message = "roomId는 1 이상 이어야 합니다.") Long roomId) { + Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); + return BaseResponse.onSuccess( + recruitCommandService.joinStudy(userId, roomId) + ); + } + + @DeleteMapping("/{roomId}/leave") + @Operation(summary = "스터디 나가기 API", description = "스터디에서 나가는 API 입니다.") + public BaseResponse leaveStudy( + @RequestHeader("Authorization") String authorizationHeader, + @PathVariable @Min(value = 1, message = "roomId는 1 이상 이어야 합니다.") Long roomId) { + Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); + recruitCommandService.leaveStudy(userId, roomId); + return BaseResponse.onSuccess(null); + } + + @DeleteMapping("/{roomId}/kick/{targetId}") + @Operation(summary = "스터디 내보내기 API", description = "스터디에서 내보내는 API 입니다.") + public BaseResponse kickStudy( + @RequestHeader("Authorization") String authorizationHeader, + @PathVariable @Min(value = 1, message = "roomId는 1 이상 이어야 합니다.") Long roomId, + @PathVariable @Min(value = 1, message = "targetId 1 이상 이어야 합니다.") Long targetId) { + Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); + recruitCommandService.kickStudy(userId, roomId, targetId); + return BaseResponse.onSuccess(null); + } } diff --git a/src/main/java/gaji/service/domain/recruit/web/dto/RecruitRequestDTO.java b/src/main/java/gaji/service/domain/recruit/web/dto/RecruitRequestDTO.java index 3ba8e1a2..65d2a915 100644 --- a/src/main/java/gaji/service/domain/recruit/web/dto/RecruitRequestDTO.java +++ b/src/main/java/gaji/service/domain/recruit/web/dto/RecruitRequestDTO.java @@ -72,9 +72,9 @@ public static class CreateRoomDTO { @Min(value = 1, message = "최대 인원은 1이상 이어야 합니다.") private int peopleMaximum; - @Schema(description = "카테고리 목록") - @ExistCategory - private CategoryEnum category; + //@Schema(description = "카테고리의 id") + //@Min(value = 1, message = "id는 1이상 이어야 합니다.") + private String category; } @Schema(description = "스터디 댓글 작성 DTO") diff --git a/src/main/java/gaji/service/domain/recruit/web/dto/RecruitResponseDTO.java b/src/main/java/gaji/service/domain/recruit/web/dto/RecruitResponseDTO.java index 1f3c3b67..7b988d3e 100644 --- a/src/main/java/gaji/service/domain/recruit/web/dto/RecruitResponseDTO.java +++ b/src/main/java/gaji/service/domain/recruit/web/dto/RecruitResponseDTO.java @@ -1,6 +1,5 @@ package gaji.service.domain.recruit.web.dto; - import gaji.service.domain.enums.CategoryEnum; import gaji.service.domain.enums.RecruitPostTypeEnum; import gaji.service.domain.enums.UserActive; @@ -39,8 +38,10 @@ public static class studyDetailResponseDTO { RecruitPostTypeEnum recruitPostTypeEnum; CategoryEnum studyCategory; int views; - int likes; - int bookmarks; + int likeCnt; + int bookmarkCnt; + Boolean likeStatus; + Boolean bookmarkStatus; LocalDate recruitStartTime; LocalDate recruitEndTime; @@ -142,6 +143,15 @@ public static class DefaultPreviewDTO { @AllArgsConstructor public static class DefaultPreviewListResponseDTO { List defaultPreviewList; - int nextIndex; + boolean hasNext; + int nextCategoryId; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class JoinStudyResponseDTO { + Long roomId; } } diff --git a/src/main/java/gaji/service/domain/room/code/RoomErrorStatus.java b/src/main/java/gaji/service/domain/room/code/RoomErrorStatus.java index 512654e2..7bd7f044 100644 --- a/src/main/java/gaji/service/domain/room/code/RoomErrorStatus.java +++ b/src/main/java/gaji/service/domain/room/code/RoomErrorStatus.java @@ -27,7 +27,9 @@ public enum RoomErrorStatus implements BaseErrorCodeInterface { _NOTICE_NOT_FOUND(HttpStatus.BAD_REQUEST, "ROOM_4007"," 공지사항울 찾을 수 없습니다."), - _ASSIGNMENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "ASSIGNMENT_4001"," 공지사항울 찾을 수 없습니다."); + _ASSIGNMENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "ASSIGNMENT_4001"," 공지사항울 찾을 수 없습니다."), + _INTERNAL_SERVER_ERROR(HttpStatus.BAD_REQUEST, "SERVER_4001"," 에러"); + diff --git a/src/main/java/gaji/service/domain/room/entity/NoticeConfirmation.java b/src/main/java/gaji/service/domain/room/entity/NoticeConfirmation.java index 1199218f..fd04f49e 100644 --- a/src/main/java/gaji/service/domain/room/entity/NoticeConfirmation.java +++ b/src/main/java/gaji/service/domain/room/entity/NoticeConfirmation.java @@ -5,8 +5,6 @@ import jakarta.persistence.*; import lombok.*; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) diff --git a/src/main/java/gaji/service/domain/room/entity/Way.java b/src/main/java/gaji/service/domain/room/entity/Way.java deleted file mode 100644 index 35c3fa61..00000000 --- a/src/main/java/gaji/service/domain/room/entity/Way.java +++ /dev/null @@ -1,20 +0,0 @@ -package gaji.service.domain.room.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Way { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String name; -} diff --git a/src/main/java/gaji/service/domain/room/repository/RoomNoticeRepository.java b/src/main/java/gaji/service/domain/room/repository/RoomNoticeRepository.java index 341773c4..502aa90a 100644 --- a/src/main/java/gaji/service/domain/room/repository/RoomNoticeRepository.java +++ b/src/main/java/gaji/service/domain/room/repository/RoomNoticeRepository.java @@ -1,18 +1,36 @@ package gaji.service.domain.room.repository; import gaji.service.domain.room.entity.RoomNotice; +import gaji.service.domain.room.web.dto.RoomResponseDto; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + @Repository public interface RoomNoticeRepository extends JpaRepository { - @Modifying - @Query("UPDATE RoomNotice rn SET rn.confirmCount = rn.confirmCount + 1 WHERE rn.id = :noticeId") - void incrementConfirmCount(Long noticeId); + @Query("SELECT NEW gaji.service.domain.room.web.dto.RoomResponseDto$NoticeDto(" + + "rn.id, sm.user.name, rn.title, rn.body, CAST(COUNT(nc) AS Long), rn.createdAt, rn.viewCount) " + + "FROM RoomNotice rn " + + "JOIN rn.studyMate sm " + + "LEFT JOIN NoticeConfirmation nc ON nc.roomNotice.id = rn.id " + + "WHERE sm.room.id = :roomId AND rn.createdAt <= :lastCreatedAt " + + "GROUP BY rn.id, sm.user.name, rn.title, rn.body, rn.createdAt, rn.viewCount " + + "ORDER BY rn.createdAt DESC, rn.id DESC") + List findNoticeSummariesForInfiniteScroll( + @Param("roomId") Long roomId, + @Param("lastCreatedAt") LocalDateTime lastCreatedAt, + Pageable pageable); - @Modifying - @Query("UPDATE RoomNotice rn SET rn.confirmCount = rn.confirmCount - 1 WHERE rn.id = :noticeId AND rn.confirmCount > 0") - void decrementConfirmCount(Long noticeId); + @Query("SELECT CASE " + + "WHEN EXISTS (SELECT 1 FROM RoomNotice rn WHERE rn.studyMate.room.id = :roomId AND rn.id = :noticeId) " + + "THEN (SELECT rn.createdAt FROM RoomNotice rn WHERE rn.studyMate.room.id = :roomId AND rn.id = :noticeId) " + + "ELSE (SELECT MAX(rn.createdAt) FROM RoomNotice rn WHERE rn.studyMate.room.id = :roomId) " + + "END") + Optional findCreatedAtByIdOrEarliest(@Param("roomId") Long roomId, @Param("noticeId") Long noticeId); } diff --git a/src/main/java/gaji/service/domain/room/repository/WeeklyUserProgressRepository.java b/src/main/java/gaji/service/domain/room/repository/WeeklyUserProgressRepository.java index 31d7cbf6..6376ba9d 100644 --- a/src/main/java/gaji/service/domain/room/repository/WeeklyUserProgressRepository.java +++ b/src/main/java/gaji/service/domain/room/repository/WeeklyUserProgressRepository.java @@ -15,12 +15,12 @@ public interface WeeklyUserProgressRepository extends JpaRepository { Optional findByRoomEventAndUser(RoomEvent roomEvent, User user); - @Query("SELECT w.user.name as name, w.progressPercentage as progressPercentage " + + @Query("SELECT w.user.nickname as nickname, w.progressPercentage as progressPercentage " + "FROM WeeklyUserProgress w WHERE w.roomEvent.id = :roomEventId") List findProgressByRoomEventId(@Param("roomEventId") Long roomEventId); interface UserProgressProjection { - String getName(); + String getNickname(); Double getProgressPercentage(); } } diff --git a/src/main/java/gaji/service/domain/room/service/RoomCommandService.java b/src/main/java/gaji/service/domain/room/service/RoomCommandService.java index 985eff35..c519b54a 100644 --- a/src/main/java/gaji/service/domain/room/service/RoomCommandService.java +++ b/src/main/java/gaji/service/domain/room/service/RoomCommandService.java @@ -10,11 +10,15 @@ import gaji.service.domain.user.entity.User; import jakarta.transaction.Transactional; +import java.util.List; + public interface RoomCommandService { @Transactional - Assignment createAssignment(Long roomId, Long userId, RoomRequestDto.AssignmentDto requestDto); - void createUserAssignmentsForStudyMembers(Assignment assignment); + + //과제생성1 + List createAssignment(Long roomId, Long userId, Integer weeks, RoomRequestDto.AssignmentDto requestDto); + @Transactional RoomNotice createNotice(Long roomId, Long userId, RoomRequestDto.RoomNoticeDto requestDto); @@ -26,6 +30,7 @@ public interface RoomCommandService { void saveRoom(Room room); + RoomResponseDto.AssignmentProgressResponse toggleAssignmentCompletion(Long userId, Long userAssignmentId); WeeklyUserProgress calculateAndSaveProgress(RoomEvent roomEvent, User user); diff --git a/src/main/java/gaji/service/domain/room/service/RoomCommandServiceImpl.java b/src/main/java/gaji/service/domain/room/service/RoomCommandServiceImpl.java index c60eb5a2..f4fc561c 100644 --- a/src/main/java/gaji/service/domain/room/service/RoomCommandServiceImpl.java +++ b/src/main/java/gaji/service/domain/room/service/RoomCommandServiceImpl.java @@ -9,7 +9,6 @@ import gaji.service.domain.room.repository.*; import gaji.service.domain.room.web.dto.RoomRequestDto; import gaji.service.domain.room.web.dto.RoomResponseDto; -import gaji.service.domain.studyMate.code.StudyMateErrorStatus; import gaji.service.domain.studyMate.entity.Assignment; import gaji.service.domain.studyMate.entity.StudyMate; import gaji.service.domain.studyMate.entity.UserAssignment; @@ -21,14 +20,18 @@ import gaji.service.global.exception.RestApiException; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.List; @Service @RequiredArgsConstructor @Transactional +@Slf4j public class RoomCommandServiceImpl implements RoomCommandService { private final AssignmentRepository assignmentRepository; @@ -47,28 +50,25 @@ public class RoomCommandServiceImpl implements RoomCommandService { //과제생성1 @Override - public Assignment createAssignment(Long roomId, Long userId, RoomRequestDto.AssignmentDto requestDto){ -// // 현재 로그인한 사용자의 정보를 가져옵니다. 추후 주석 해제 -// String username = SecurityContextHolder.getContext().getAuthentication().getName(); -// User currentUser = userRepository.findByUsername(username) -// .orElseThrow(() -> new RestApiException(PostErrorStatus._USER_NOT_FOUND)); - - - RoomEvent roomEvent = roomQueryService.findRoomEventByRoomIdAndWeeks(roomId, requestDto.getWeeks()); - - // List을 단일 String으로 변환 - String bodyContent = String.join(", ", requestDto.getBodyList()); + public List createAssignment(Long roomId, Long userId, Integer weeks, RoomRequestDto.AssignmentDto requestDto) { + RoomEvent roomEvent = roomQueryService.findRoomEventByRoomIdAndWeeks(roomId, weeks); + List savedAssignments = new ArrayList<>(); + + for (String body : requestDto.getBodyList()) { + Assignment assignment = Assignment.builder() + .roomEvent(roomEvent) + .body(body) + .build(); + Assignment savedAssignment = assignmentRepository.save(assignment); + savedAssignments.add(savedAssignment); - Assignment assignment = Assignment.builder() - .roomEvent(roomEvent) - .body(bodyContent) - .build(); + createUserAssignmentsForStudyMembers(savedAssignment); + } - Assignment savedAssignment = assignmentRepository.save(assignment); + updateWeeklyUserProgressForNewAssignments(savedAssignments); - createUserAssignmentsForStudyMembers(savedAssignment); - return savedAssignment; + return savedAssignments; } @Override @@ -89,11 +89,13 @@ public RoomNotice createNotice(Long roomId, Long userId, RoomRequestDto.RoomNoti // 과제 생성할 때 user에게 할당해주는 메서드 @Override public void createUserAssignmentsForStudyMembers(Assignment assignment) { - List studyMates = studyMateRepository.findByRoom(assignment.getRoomEvent().getRoom()); + for (StudyMate studyMate : studyMates) { + User user = studyMate.getUser(); + UserAssignment userAssignment = UserAssignment.builder() - .user(studyMate.getUser()) + .user(user) .assignment(assignment) .isComplete(false) .build(); @@ -105,7 +107,7 @@ public void createUserAssignmentsForStudyMembers(Assignment assignment) { public RoomEvent setStudyPeriod(Long roomId, Integer weeks, Long userId, RoomRequestDto.StudyPeriodDto requestDto) { User user = userQueryService.findUserById(userId); Room room = roomQueryService.findRoomById(roomId); - StudyMate studyMate = studyMateQueryService.findByUserIdAndRoomId(roomId, user.getId()); + StudyMate studyMate = studyMateQueryService.findByUserIdAndRoomId(user.getId(),roomId); if (!studyMate.getRole().equals(Role.READER)) { throw new RestApiException(RoomErrorStatus._USER_NOT_READER_IN_ROOM); @@ -133,7 +135,7 @@ public RoomEvent setStudyPeriod(Long roomId, Integer weeks, Long userId, RoomReq public RoomEvent setStudyDescription(Long roomId, Integer weeks, Long userId, RoomRequestDto.StudyDescriptionDto requestDto) { User user = userQueryService.findUserById(userId); Room room = roomQueryService.findRoomById(roomId); - StudyMate studyMate = studyMateQueryService.findByUserIdAndRoomId(roomId, user.getId()); + StudyMate studyMate = studyMateQueryService.findByUserIdAndRoomId(user.getId(),roomId); if (!studyMate.getRole().equals(Role.READER)) { @@ -193,7 +195,6 @@ public void saveRoom(Room room) { - @Transactional @Override public RoomResponseDto.AssignmentProgressResponse toggleAssignmentCompletion(Long userId, Long userAssignmentId) { UserAssignment userAssignment = userAssignmentRepository.findById(userAssignmentId) @@ -203,19 +204,16 @@ public RoomResponseDto.AssignmentProgressResponse toggleAssignmentCompletion(Lon RoomEvent roomEvent = userAssignment.getAssignment().getRoomEvent(); // Toggle completion status - if(!userAssignment.isComplete()) { - userAssignment.setComplete(true); - userAssignmentRepository.save(userAssignment); - }else{ - userAssignment.setComplete(false); - userAssignmentRepository.save(userAssignment); - } + userAssignment.setComplete(!userAssignment.isComplete()); + userAssignmentRepository.save(userAssignment); + // Calculate and save progress WeeklyUserProgress progress = calculateAndSaveProgress(roomEvent, user); // Prepare response boolean isCompleted = progress.getProgressPercentage() >= 100.0; LocalDate deadline = roomEvent.getEndTime(); + long daysLeft = ChronoUnit.DAYS.between(LocalDate.now(), deadline); return RoomResponseDto.AssignmentProgressResponse.builder() .progressPercentage(progress.getProgressPercentage()) @@ -223,9 +221,11 @@ public RoomResponseDto.AssignmentProgressResponse toggleAssignmentCompletion(Lon .totalAssignments(progress.getTotalAssignments()) .isCompleted(isCompleted) .deadline(deadline) + .daysLeft(daysLeft) .build(); } + @Override public WeeklyUserProgress calculateAndSaveProgress(RoomEvent roomEvent, User user) { int totalAssignments = roomEvent.getAssignmentList().size(); @@ -254,5 +254,39 @@ public WeeklyUserProgress calculateAndSaveProgress(RoomEvent roomEvent, User use return weeklyUserProgressRepository.save(progress); } + private void updateWeeklyUserProgressForNewAssignment(Assignment assignment) { + RoomEvent roomEvent = assignment.getRoomEvent(); + List studyMates = studyMateRepository.findByRoom(roomEvent.getRoom()); + + for (StudyMate studyMate : studyMates) { + User user = studyMate.getUser(); + WeeklyUserProgress progress = weeklyUserProgressRepository + .findByRoomEventAndUser(roomEvent, user) + .orElseGet(() -> WeeklyUserProgress.createInitialProgress(user, roomEvent, 0)); + + progress.updateProgress(progress.getCompletedAssignments()); + weeklyUserProgressRepository.save(progress); + } + } + private void updateWeeklyUserProgressForNewAssignments(List assignments) { + if (assignments.isEmpty()) { + return; + } + + RoomEvent roomEvent = assignments.get(0).getRoomEvent(); + List studyMates = studyMateRepository.findByRoom(roomEvent.getRoom()); + + for (StudyMate studyMate : studyMates) { + User user = studyMate.getUser(); + WeeklyUserProgress progress = weeklyUserProgressRepository + .findByRoomEventAndUser(roomEvent, user) + .orElseGet(() -> WeeklyUserProgress.createInitialProgress(user, roomEvent, 0)); + + int totalAssignments = progress.getTotalAssignments() + assignments.size(); + progress.setTotalAssignments(totalAssignments); + progress.updateProgress(progress.getCompletedAssignments()); + weeklyUserProgressRepository.save(progress); + } + } } diff --git a/src/main/java/gaji/service/domain/room/service/RoomQueryService.java b/src/main/java/gaji/service/domain/room/service/RoomQueryService.java index 2e26d487..c9a259ec 100644 --- a/src/main/java/gaji/service/domain/room/service/RoomQueryService.java +++ b/src/main/java/gaji/service/domain/room/service/RoomQueryService.java @@ -13,15 +13,18 @@ public interface RoomQueryService { RoomEvent findRoomEventByRoomIdAndWeeks(Long roomId, Integer weeks); - List getNotices(Long roomId, int page, int size); +// List getNotices(Long roomId, int page, int size); +// +// RoomResponseDto.NoticeDto getNoticeDetail(Long roomId, Long noticeId); +// +// List getNextNotices(Long roomId, Long lastNoticeId, int size); - RoomResponseDto.NoticeDto getNoticeDetail(Long roomId, Long noticeId); + List getNextNotices(Long roomId, Long lastNoticeId, int size); @Transactional(readOnly = true) - RoomResponseDto.WeeklyStudyInfoDTO getWeeklyStudyInfo(Long roomEventId); + RoomResponseDto.WeeklyStudyInfoDTO getWeeklyStudyInfo(Long roomId, Integer weeks); - @Transactional(readOnly = true) - List getUserProgressByRoomEventId(Long roomEventId); + List getUserProgressByRoomEventId(Long roomId, Integer weeks); RoomResponseDto.RoomMainDto getMainStudyRoom(Long roomId); diff --git a/src/main/java/gaji/service/domain/room/service/RoomQueryServiceImpl.java b/src/main/java/gaji/service/domain/room/service/RoomQueryServiceImpl.java index a5b83520..d9b33bc1 100644 --- a/src/main/java/gaji/service/domain/room/service/RoomQueryServiceImpl.java +++ b/src/main/java/gaji/service/domain/room/service/RoomQueryServiceImpl.java @@ -6,12 +6,17 @@ import gaji.service.domain.room.entity.RoomEvent; import gaji.service.domain.room.repository.*; import gaji.service.domain.room.web.dto.RoomResponseDto; +import gaji.service.global.converter.DateConverter; import gaji.service.global.exception.RestApiException; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.webjars.NotFoundException; +import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -26,8 +31,10 @@ public class RoomQueryServiceImpl implements RoomQueryService { private final RoomQueryRepository roomQueryRepository; private final WeeklyUserProgressRepository weeklyUserProgressRepository; private final NoticeConfirmationRepository noticeConfirmationRepository; + private final WebInvocationPrivilegeEvaluator privilegeEvaluator; + private final RoomNoticeRepository roomNoticeRepository; -// @Override + // @Override // public RoomEvent findRoomEventById(Long roomId){ // return roomEventRepository.findRoomEventById(roomId) // .orElseThrow(() -> new RestApiException(RoomErrorStatus._ROOM_EVENT_NOT_FOUND)); @@ -46,32 +53,36 @@ public RoomEvent findRoomEventByRoomIdAndWeeks(Long roomId, Integer weeks) { .orElseThrow(() -> new RestApiException(RoomErrorStatus._ROOM_EVENT_NOT_FOUND)); } - @Override - public List getNotices(Long roomId, int page, int size) { - return roomQueryRepository.getNotices(roomId, page, size); - } +// @Override +// public List getNotices(Long roomId, int page, int size) { +// return roomQueryRepository.getNotices(roomId, page, size); +// } @Override - @Transactional(readOnly = false) // readOnly = false로 설정 - public RoomResponseDto.NoticeDto getNoticeDetail(Long roomId, Long noticeId) { - RoomResponseDto.NoticeDto notice = roomQueryRepository.getNotices(roomId, 1, Integer.MAX_VALUE).stream() - .filter(n -> n.getId().equals(noticeId)) - .findFirst() - .orElseThrow(() -> new NotFoundException("Notice not found")); - - // viewCount 증가 - roomQueryRepository.incrementViewCount (noticeId); - - // 증가된 viewCount를 반영하기 위해 notice 객체 업데이트 - notice.setViewCount(notice.getViewCount() + 1); - - return notice; + public List getNextNotices(Long roomId, Long lastNoticeId, int size) { + LocalDateTime lastCreatedAt; + if (lastNoticeId == 0) { + lastCreatedAt = LocalDateTime.now(); + } else { + lastCreatedAt = roomNoticeRepository.findCreatedAtByIdOrEarliest(roomId, lastNoticeId) + .orElseThrow(() -> new RestApiException(RoomErrorStatus._NOTICE_NOT_FOUND)); + } + + Sort sort = Sort.by(Sort.Direction.DESC, "createdAt", "id"); + Pageable pageable = PageRequest.of(0, size, sort); + + List notices = roomNoticeRepository.findNoticeSummariesForInfiniteScroll(roomId, lastCreatedAt, pageable); + + LocalDateTime now = LocalDateTime.now(); + for (RoomResponseDto.NoticeDto notice : notices) { + notice.setTimeSincePosted(DateConverter.convertToRelativeTimeFormat(notice.getCreatedAt())); + } + return notices; } - @Override @Transactional(readOnly = true) - public RoomResponseDto.WeeklyStudyInfoDTO getWeeklyStudyInfo(Long roomEventId) { - RoomEvent roomEvent = roomEventRepository.findById(roomEventId) + public RoomResponseDto.WeeklyStudyInfoDTO getWeeklyStudyInfo(Long roomId, Integer weeks) { + RoomEvent roomEvent = roomEventRepository.findRoomEventByRoomIdAndWeeks(roomId, weeks) .orElseThrow(() -> new RestApiException(RoomErrorStatus._ROOM_EVENT_NOT_FOUND)); return RoomResponseDto.WeeklyStudyInfoDTO.builder() @@ -83,13 +94,16 @@ public RoomResponseDto.WeeklyStudyInfoDTO getWeeklyStudyInfo(Long roomEventId) { } @Override - public List getUserProgressByRoomEventId(Long roomEventId) { - List projections = - weeklyUserProgressRepository.findProgressByRoomEventId(roomEventId); + public List getUserProgressByRoomEventId(Long roomId, Integer weeks) { + RoomEvent roomEvent = roomEventRepository.findRoomEventByRoomIdAndWeeks(roomId, weeks) + .orElseThrow(() -> new RestApiException(RoomErrorStatus._ROOM_EVENT_NOT_FOUND)); + + List projections = + weeklyUserProgressRepository.findProgressByRoomEventId(roomEvent.getId()); return projections.stream() .map(projection -> RoomResponseDto.UserProgressDTO.builder() - .name(projection.getName()) + .nickname(projection.getNickname()) .progressPercentage(projection.getProgressPercentage()) .build()) .collect(Collectors.toList()); diff --git a/src/main/java/gaji/service/domain/room/web/controller/RoomMainController.java b/src/main/java/gaji/service/domain/room/web/controller/RoomMainController.java index 5d5fac97..54dcab95 100644 --- a/src/main/java/gaji/service/domain/room/web/controller/RoomMainController.java +++ b/src/main/java/gaji/service/domain/room/web/controller/RoomMainController.java @@ -26,17 +26,22 @@ public class RoomMainController { private final RoomQueryService roomQueryService; private final TokenProviderService tokenProviderService; - @PostMapping("/assignments/{roomId}/{userId}") - @Operation(summary = "스터디룸 과제 등록 API",description = "스터디룸의 과제를 등록하는 API입니다. room의 id가 존재하는지, 스터디에 참혀하고 있는 user인지 검증합니다.") - public BaseResponse AssignmentController( + @PostMapping("/assignments/{roomId}/{weeks}") + @Operation(summary = "스터디룸 과제 등록 API", description = "스터디룸의 과제를 등록하는 API입니다. room의 id가 존재하는지, 스터디에 참여하고 있는 user인지 검증합니다.") + public BaseResponse createAssignments( @RequestBody @Valid RoomRequestDto.AssignmentDto requestDto, @PathVariable Long roomId, - @RequestHeader("Authorization") String authorizationHeader){ + @PathVariable Integer weeks, + @RequestHeader("Authorization") String authorizationHeader) { - Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); - Assignment assignment = roomCommandService.createAssignment(roomId, userId, requestDto); - RoomResponseDto.AssignmentResponseDto responseDto = RoomResponseDto.AssignmentResponseDto.of(assignment.getId()); - return BaseResponse.onSuccess(responseDto); + Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); + List assignments = roomCommandService.createAssignment(roomId, userId, weeks, requestDto); + + // RoomEvent 가져오기 + RoomEvent roomEvent = roomQueryService.findRoomEventByRoomIdAndWeeks(roomId, weeks); + + RoomResponseDto.AssignmentResponseDto responseDto = RoomResponseDto.AssignmentResponseDto.of(assignments, roomEvent); + return BaseResponse.onSuccess(responseDto); } @PostMapping("/event/{roomId}/{weeks}/period") @@ -60,37 +65,43 @@ public BaseResponse setStudyDescription( @PathVariable Long roomId, @RequestHeader("Authorization") String authorizationHeader, @RequestBody @Valid RoomRequestDto.StudyDescriptionDto requestDto) { + Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); + System.out.println(roomId); + System.out.println("회원id: " + userId); RoomEvent event = roomCommandService.setStudyDescription(roomId, weeks, userId, requestDto); RoomResponseDto.EventResponseDto responseDto = RoomResponseDto.EventResponseDto.of(event.getId()); return BaseResponse.onSuccess(responseDto); } - @PostMapping("/main/assignment/{userAssignmentId}") - @Operation(summary = "주차별 과제 체크 박스 체크", description = "과제 체크 박스를 클릭하면 과제 완료 .") - public ResponseEntity toggleAssignmentCompletion( + @Operation(summary = "주차별 과제 체크 박스 체크", description = "과제 체크 박스를 클릭하면 과제 완료 상태를 토글합니다.") + public ResponseEntity toggleAssignmentCompletion( @RequestHeader("Authorization") String authorizationHeader, @PathVariable Long userAssignmentId) { - - Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); - RoomResponseDto.AssignmentProgressResponse response = roomCommandService.toggleAssignmentCompletion(userId, userAssignmentId); - return ResponseEntity.ok(response); + Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader); + RoomResponseDto.AssignmentProgressResponse response = roomCommandService.toggleAssignmentCompletion(userId, userAssignmentId); + return ResponseEntity.ok(response); } + // 수정 필요 //특정 스터디룸의 모든 사용자의 진행 상황을 조회합니다 - @GetMapping("/{roomEventId}/progress") + @GetMapping("/{roomId}/{weeks}/progress") @Operation(summary = "주차별 과제 진행율", description = "특정 스터디룸의 모든 사용자의 진행 상황을 조회합니다.") - public ResponseEntity> getStudyMateProgress(@PathVariable Long roomEventId) { - List progressList = roomQueryService.getUserProgressByRoomEventId(roomEventId); + public ResponseEntity> getStudyMateProgress( + @PathVariable Long roomId, + @PathVariable Integer weeks) { + List progressList = roomQueryService.getUserProgressByRoomEventId(roomId, weeks); return ResponseEntity.ok(progressList); } - @GetMapping("/events/{roomEventId}/weekly-info") + @GetMapping("/events/{roomId}/{weeks}/weekly-info") @Operation(summary = "주차별 스터디 정보", description = "특정 주차의 스터디 정보를 조회합니다.") - public ResponseEntity getWeeklyStudyInfo(@PathVariable Long roomEventId) { - RoomResponseDto.WeeklyStudyInfoDTO weeklyInfo = roomQueryService.getWeeklyStudyInfo(roomEventId); + public ResponseEntity getWeeklyStudyInfo( + @PathVariable Long roomId, + @PathVariable Integer weeks) { + RoomResponseDto.WeeklyStudyInfoDTO weeklyInfo = roomQueryService.getWeeklyStudyInfo(roomId, weeks); return ResponseEntity.ok(weeklyInfo); } diff --git a/src/main/java/gaji/service/domain/room/web/controller/RoomNoticeController.java b/src/main/java/gaji/service/domain/room/web/controller/RoomNoticeController.java index 73fc0d37..6b921e00 100644 --- a/src/main/java/gaji/service/domain/room/web/controller/RoomNoticeController.java +++ b/src/main/java/gaji/service/domain/room/web/controller/RoomNoticeController.java @@ -1,9 +1,7 @@ package gaji.service.domain.room.web.controller; import gaji.service.domain.room.converter.RoomConverter; -import gaji.service.domain.room.entity.QNoticeConfirmation; import gaji.service.domain.room.entity.RoomNotice; -import gaji.service.domain.room.repository.NoticeConfirmationRepository; import gaji.service.domain.room.service.RoomCommandService; import gaji.service.domain.room.service.RoomQueryService; import gaji.service.domain.room.web.dto.RoomRequestDto; @@ -11,6 +9,8 @@ import gaji.service.global.base.BaseResponse; import gaji.service.jwt.service.TokenProviderService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -42,17 +42,17 @@ public BaseResponse NoticeController( } @GetMapping("/{roomId}/notices") - @Operation(summary = "스터디룸 공지 목록 조회 API") - public BaseResponse getNotices( - @PathVariable Long roomId, - @RequestParam(defaultValue = "1") int page, - @RequestParam(defaultValue = "5") int size) { - List notices = roomQueryService.getNotices(roomId, page, size); - return BaseResponse.onSuccess( - new RoomResponseDto.NoticeDtoList(notices) - ); + @Operation(summary = "스터디룸 공지 무한 스크롤 조회", description = "공지를 무한 스크롤 방식으로 조회합니다.") + @ApiResponse(responseCode = "200", description = "조회 성공") + public BaseResponse> getNextNotices( + @PathVariable @Parameter(description = "스터디룸 ID") Long roomId, + @RequestParam @Parameter(description = "마지막으로 로드된 공지 ID") Long lastNoticeId, + @RequestParam(defaultValue = "5") @Parameter(description = "조회할 공지 수") int size) { + List notices = roomQueryService.getNextNotices(roomId, lastNoticeId, size); + return BaseResponse.onSuccess(notices); } + // @GetMapping("/notice/{noticeId}") // @Operation(summary = "특정 공지사항을 조회하는 API") // public ResponseEntity getNoticeDetail( diff --git a/src/main/java/gaji/service/domain/room/web/dto/RoomRequestDto.java b/src/main/java/gaji/service/domain/room/web/dto/RoomRequestDto.java index 4ec62cc5..6d21143b 100644 --- a/src/main/java/gaji/service/domain/room/web/dto/RoomRequestDto.java +++ b/src/main/java/gaji/service/domain/room/web/dto/RoomRequestDto.java @@ -2,7 +2,10 @@ import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -21,11 +24,6 @@ public class RoomRequestDto { @Builder public static class AssignmentDto { - @Schema(description = "주차") - @NotNull(message = "주차를 입력해주세요.") - @Min(value = 1, message = "유효하지 않은 형식의 주차입니다.") - private Integer weeks; - @Schema(description = "과제 입력") @NotEmpty(message = "1개 이상의 과제를 입력해주세요.") private List bodyList = new ArrayList<>(); diff --git a/src/main/java/gaji/service/domain/room/web/dto/RoomResponseDto.java b/src/main/java/gaji/service/domain/room/web/dto/RoomResponseDto.java index d5802ef3..77b6c177 100644 --- a/src/main/java/gaji/service/domain/room/web/dto/RoomResponseDto.java +++ b/src/main/java/gaji/service/domain/room/web/dto/RoomResponseDto.java @@ -1,28 +1,82 @@ package gaji.service.domain.room.web.dto; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import gaji.service.domain.room.entity.RoomEvent; +import gaji.service.domain.studyMate.entity.Assignment; +import gaji.service.domain.studyMate.entity.WeeklyUserProgress; +import lombok.*; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.List; +import java.util.stream.Collectors; public class RoomResponseDto { @Getter + @Setter @Builder public static class AssignmentResponseDto { - private Long assignmentId; + private List assignmentIds; + private LocalDate deadline; + private Long daysLeft; + + public static AssignmentResponseDto of(List assignments, RoomEvent roomEvent) { + List ids = assignments.stream() + .map(Assignment::getId) + .collect(Collectors.toList()); - public static AssignmentResponseDto of(Long assignmentId) { return AssignmentResponseDto.builder() - .assignmentId(assignmentId) + .assignmentIds(ids) + .deadline(roomEvent.getEndTime()) + .daysLeft(calculateDaysLeft(roomEvent.getEndTime())) .build(); } } + @Getter + @Setter + @Builder + public static class AssignmentProgressResponse { + @JsonProperty("progressPercentage") + private Double progressPercentage; + + @JsonProperty("completedAssignments") + private Integer completedAssignments; + + @JsonProperty("totalAssignments") + private Integer totalAssignments; + + @JsonProperty("isCompleted") + private Boolean isCompleted; + + @JsonProperty("deadline") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") + private LocalDate deadline; + + + @JsonProperty("daysLeft") + private Long daysLeft; + + public static AssignmentProgressResponse of(WeeklyUserProgress progress, RoomEvent roomEvent) { + return AssignmentProgressResponse.builder() + .progressPercentage(progress.getProgressPercentage()) + .completedAssignments(progress.getCompletedAssignments()) + .totalAssignments(progress.getTotalAssignments()) + .isCompleted(progress.getProgressPercentage() >= 100.0) + .deadline(roomEvent.getEndTime()) + .daysLeft(calculateDaysLeft(roomEvent.getEndTime())) + .build(); + } + } + + public static long calculateDaysLeft(LocalDate deadline) { + return ChronoUnit.DAYS.between(LocalDate.now(), deadline); + } + + @Getter @Builder public static class EventResponseDto { @@ -39,7 +93,7 @@ public static EventResponseDto of(Long eventId) { @Getter @NoArgsConstructor @AllArgsConstructor - public static class AssignmentDto{ + public static class AssignmentDto { Long id; Integer weeks; String body; @@ -49,7 +103,7 @@ public static class AssignmentDto{ @Getter @NoArgsConstructor @AllArgsConstructor - public static class RoomNoticeDto{ + public static class RoomNoticeDto { Long noticeId; } @@ -65,7 +119,6 @@ public static class RoomMainDto { private Long daysLeftForRecruit; private Long applicantCount; - // 수정된 생성자 public RoomMainDto(String name, LocalDate startDay, LocalDate endDay, LocalDate recruitStartDay, LocalDate recruitEndDay, Long daysLeftForRecruit, Long applicantCount) { @@ -75,17 +128,10 @@ public RoomMainDto(String name, LocalDate startDay, LocalDate endDay, this.recruitStartDay = recruitStartDay; this.recruitEndDay = recruitEndDay; this.applicantCount = applicantCount; - - if(daysLeftForRecruit < 0 ){ - this.daysLeftForRecruit = 0L; - }else{ - this.daysLeftForRecruit = daysLeftForRecruit; - } - + this.daysLeftForRecruit = Math.max(daysLeftForRecruit, 0L); } } - @Builder @Getter @NoArgsConstructor @@ -107,7 +153,7 @@ public static class NoticePreview { @Getter @AllArgsConstructor - public static class NoticeDtoList{ + public static class NoticeDtoList { private List noticeDtoList; } @@ -125,7 +171,6 @@ public static class NoticeDto { private Integer viewCount; private String timeSincePosted; - // 이 생성자를 추가합니다 public NoticeDto(Long id, String authorName, String title, String body, Long confirmCount, LocalDateTime createdAt, Integer viewCount) { this.id = id; this.authorName = authorName; @@ -145,37 +190,10 @@ public void setViewCount(Integer viewCount) { } } - @Getter - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class AssignmentProgressResponse { - private Double progressPercentage; - private Integer completedAssignments; - private Integer totalAssignments; - private Boolean isCompleted; - private LocalDate deadline; - - // Custom method to check if the deadline has passed - public boolean isDeadlinePassed() { - return LocalDate.now().isAfter(deadline); - } - - // Custom method to get remaining days until deadline - public long getRemainingDays() { - return LocalDate.now().until(deadline).getDays(); - } - - // Custom method to get a formatted string of progress - public String getFormattedProgress() { - return String.format("%.1f%%", progressPercentage); - } - } - @Getter @Builder public static class UserProgressDTO { - private String name; + private String nickname; private Double progressPercentage; } @@ -195,8 +213,6 @@ public static class StudyPeriodDTO { private LocalDate endDate; } - - // 스터디룸 메인 게시판 글 불러오기 @Builder @Getter @NoArgsConstructor @@ -211,7 +227,6 @@ public static class RoomMainNoticeDto { private Integer viewCount; private String timeSincePosted; - // 이 생성자를 추가합니다 public RoomMainNoticeDto(Long id, String authorName, String title, String body, Long confirmCount, LocalDateTime createdAt, Integer viewCount) { this.id = id; this.authorName = authorName; @@ -225,10 +240,7 @@ public RoomMainNoticeDto(Long id, String authorName, String title, String body, @Getter @AllArgsConstructor - public static class IsConfirmedResponse{ + public static class IsConfirmedResponse { private Boolean isConfirmed; } - - - -} +} \ No newline at end of file diff --git a/src/main/java/gaji/service/domain/roomBoard/repository/RoomBoardRepository.java b/src/main/java/gaji/service/domain/roomBoard/repository/RoomBoardRepository.java index f88e4b48..7ad757d6 100644 --- a/src/main/java/gaji/service/domain/roomBoard/repository/RoomBoardRepository.java +++ b/src/main/java/gaji/service/domain/roomBoard/repository/RoomBoardRepository.java @@ -1,5 +1,6 @@ package gaji.service.domain.roomBoard.repository; +import gaji.service.domain.enums.RoomPostType; import gaji.service.domain.roomBoard.entity.RoomBoard; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -8,5 +9,6 @@ @Repository public interface RoomBoardRepository extends JpaRepository { + Optional findRoomBoardByRoomIdAndRoomPostType(Long roomId, RoomPostType roomPostType); Optional findByRoomId(Long roomId); } diff --git a/src/main/java/gaji/service/domain/roomBoard/repository/RoomInfo/RoomInfoPostRepository.java b/src/main/java/gaji/service/domain/roomBoard/repository/RoomInfo/RoomInfoPostRepository.java index debb6537..a565eec2 100644 --- a/src/main/java/gaji/service/domain/roomBoard/repository/RoomInfo/RoomInfoPostRepository.java +++ b/src/main/java/gaji/service/domain/roomBoard/repository/RoomInfo/RoomInfoPostRepository.java @@ -8,18 +8,26 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; @Repository public interface RoomInfoPostRepository extends JpaRepository { @Query("SELECT new gaji.service.domain.roomBoard.web.dto.RoomPostResponseDto$InfoPostSummaryDto(" + "r.id, r.title, r.studyMate.user.nickname, r.createdAt, r.viewCount, SIZE(r.infoPostCommentList)) " + "FROM RoomInfoPost r " + - "WHERE r.roomBoard.id = :boardId AND r.id < :lastPostId " + - "ORDER BY r.createdAt DESC") + "WHERE r.roomBoard.id = :boardId AND r.createdAt <= :lastCreatedAt " + + "ORDER BY r.createdAt DESC, r.id DESC") List findInfoPostSummariesForInfiniteScroll( @Param("boardId") Long boardId, - @Param("lastPostId") Long lastPostId, + @Param("lastCreatedAt") LocalDateTime lastCreatedAt, Pageable pageable); + @Query("SELECT CASE " + + "WHEN EXISTS (SELECT 1 FROM RoomInfoPost r WHERE r.roomBoard.id = :boardId AND r.id = :postId) " + + "THEN (SELECT r.createdAt FROM RoomInfoPost r WHERE r.roomBoard.id = :boardId AND r.id = :postId) " + + "ELSE (SELECT MAX(r.createdAt) FROM RoomInfoPost r WHERE r.roomBoard.id = :boardId) " + + "END") + Optional findCreatedAtByIdOrEarliest(@Param("boardId") Long boardId, @Param("postId") Long postId); } diff --git a/src/main/java/gaji/service/domain/roomBoard/repository/RoomPost/RoomPostRepository.java b/src/main/java/gaji/service/domain/roomBoard/repository/RoomPost/RoomPostRepository.java index b7167798..1cfcc61b 100644 --- a/src/main/java/gaji/service/domain/roomBoard/repository/RoomPost/RoomPostRepository.java +++ b/src/main/java/gaji/service/domain/roomBoard/repository/RoomPost/RoomPostRepository.java @@ -8,17 +8,27 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; @Repository public interface RoomPostRepository extends JpaRepository { @Query("SELECT new gaji.service.domain.roomBoard.web.dto.RoomPostResponseDto$PostSummaryDto(" + "r.id, r.title, r.studyMate.user.nickname, r.createdAt, r.viewCount, SIZE(r.postCommentList)) " + "FROM RoomPost r " + - "WHERE r.roomBoard.id = :boardId AND r.id < :lastPostId " + - "ORDER BY r.createdAt DESC") + "WHERE r.roomBoard.id = :boardId AND r.createdAt <= :lastCreatedAt " + + "ORDER BY r.createdAt DESC, r.id DESC") List findPostSummariesForInfiniteScroll( @Param("boardId") Long boardId, - @Param("lastPostId") Long lastPostId, + @Param("lastCreatedAt") LocalDateTime lastCreatedAt, Pageable pageable); + + @Query("SELECT CASE " + + "WHEN EXISTS (SELECT 1 FROM RoomPost r WHERE r.roomBoard.id = :boardId AND r.id = :postId) " + + "THEN (SELECT r.createdAt FROM RoomPost r WHERE r.roomBoard.id = :boardId AND r.id = :postId) " + + "ELSE (SELECT MAX(r.createdAt) FROM RoomPost r WHERE r.roomBoard.id = :boardId) " + + "END") + Optional findCreatedAtByIdOrEarliest(@Param("boardId") Long boardId, @Param("postId") Long postId); + } diff --git a/src/main/java/gaji/service/domain/roomBoard/repository/RoomTrouble/RoomTroublePostRepository.java b/src/main/java/gaji/service/domain/roomBoard/repository/RoomTrouble/RoomTroublePostRepository.java index 5b5dbdb5..a6ac8b0f 100644 --- a/src/main/java/gaji/service/domain/roomBoard/repository/RoomTrouble/RoomTroublePostRepository.java +++ b/src/main/java/gaji/service/domain/roomBoard/repository/RoomTrouble/RoomTroublePostRepository.java @@ -8,17 +8,25 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; @Repository public interface RoomTroublePostRepository extends JpaRepository { @Query("SELECT new gaji.service.domain.roomBoard.web.dto.RoomPostResponseDto$TroublePostSummaryDto(" + "r.id, r.title, r.studyMate.user.nickname, r.createdAt, r.viewCount, SIZE(r.troublePostCommentList)) " + "FROM RoomTroublePost r " + - "WHERE r.roomBoard.id = :boardId AND r.id < :lastPostId " + - "ORDER BY r.createdAt DESC") + "WHERE r.roomBoard.id = :boardId AND r.createdAt <= :lastCreatedAt " + + "ORDER BY r.createdAt DESC, r.id DESC") List findTroublePostSummariesForInfiniteScroll( @Param("boardId") Long boardId, - @Param("lastPostId") Long lastPostId, + @Param("lastCreatedAt") LocalDateTime lastCreatedAt, Pageable pageable); -} + + @Query("SELECT CASE " + + "WHEN EXISTS (SELECT 1 FROM RoomTroublePost r WHERE r.roomBoard.id = :boardId AND r.id = :postId) " + + "THEN (SELECT r.createdAt FROM RoomTroublePost r WHERE r.roomBoard.id = :boardId AND r.id = :postId) " + + "ELSE (SELECT MAX(r.createdAt) FROM RoomTroublePost r WHERE r.roomBoard.id = :boardId) " + + "END") + Optional findCreatedAtByIdOrEarliest(@Param("boardId") Long boardId, @Param("postId") Long postId);} diff --git a/src/main/java/gaji/service/domain/roomBoard/repository/RoomTrouble/TroublePostCommentRepository.java b/src/main/java/gaji/service/domain/roomBoard/repository/RoomTrouble/TroublePostCommentRepository.java index e50e40b4..29a34816 100644 --- a/src/main/java/gaji/service/domain/roomBoard/repository/RoomTrouble/TroublePostCommentRepository.java +++ b/src/main/java/gaji/service/domain/roomBoard/repository/RoomTrouble/TroublePostCommentRepository.java @@ -27,4 +27,5 @@ List findCommentsWithReplies(@Param("postId") Long postId, "AND c.isReply = false " + "ORDER BY c.createdAt ASC, c.id ASC") Page findOldestComments(@Param("postId") Long postId, Pageable pageable); + } diff --git a/src/main/java/gaji/service/domain/roomBoard/service/RoomInfo/RoomInfoPostCommandServiceImpl.java b/src/main/java/gaji/service/domain/roomBoard/service/RoomInfo/RoomInfoPostCommandServiceImpl.java index 93e984ff..b0f5d1cd 100644 --- a/src/main/java/gaji/service/domain/roomBoard/service/RoomInfo/RoomInfoPostCommandServiceImpl.java +++ b/src/main/java/gaji/service/domain/roomBoard/service/RoomInfo/RoomInfoPostCommandServiceImpl.java @@ -51,11 +51,11 @@ public RoomPostResponseDto.toCreateRoomInfoPostIdDTO createRoomInfoPostIdDTO(Lon StudyMate studyMate = studyMateQueryService.findByUserIdAndRoomId(user.getId(), roomId); // 스터디룸 게시판 확인 또는 생성 - RoomBoard roomBoard = roomBoardRepository.findByRoomId(roomId) + RoomBoard roomBoard = roomBoardRepository.findRoomBoardByRoomIdAndRoomPostType(roomId, RoomPostType.ROOM_INFORMATION_POST) .orElseGet(() -> { RoomBoard newRoomBoard = RoomBoard.builder() .room(room) - .roomPostType(RoomPostType.ROOM_TROUBLE_POST) + .roomPostType(RoomPostType.ROOM_INFORMATION_POST) .name(room.getName()) .build(); return roomBoardRepository.save(newRoomBoard); diff --git a/src/main/java/gaji/service/domain/roomBoard/service/RoomInfo/RoomInfoPostQueryServiceImpl.java b/src/main/java/gaji/service/domain/roomBoard/service/RoomInfo/RoomInfoPostQueryServiceImpl.java index eb06edce..317bc428 100644 --- a/src/main/java/gaji/service/domain/roomBoard/service/RoomInfo/RoomInfoPostQueryServiceImpl.java +++ b/src/main/java/gaji/service/domain/roomBoard/service/RoomInfo/RoomInfoPostQueryServiceImpl.java @@ -1,8 +1,11 @@ package gaji.service.domain.roomBoard.service.RoomInfo; +import gaji.service.domain.enums.RoomPostType; import gaji.service.domain.roomBoard.code.RoomPostErrorStatus; +import gaji.service.domain.roomBoard.entity.RoomBoard; import gaji.service.domain.roomBoard.entity.RoomInfo.InfoPostComment; import gaji.service.domain.roomBoard.entity.RoomInfo.RoomInfoPost; +import gaji.service.domain.roomBoard.repository.RoomBoardRepository; import gaji.service.domain.roomBoard.repository.RoomInfo.InfoPostCommentRepository; import gaji.service.domain.roomBoard.repository.RoomInfo.RoomInfoPostRepository; import gaji.service.domain.roomBoard.web.dto.RoomPostResponseDto; @@ -13,6 +16,7 @@ import org.springframework.data.domain.*; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -24,6 +28,7 @@ public class RoomInfoPostQueryServiceImpl implements RoomInfoPostQueryService{ private final RoomInfoPostRepository roomInfoPostRepository; private final InfoPostCommentRepository infoPostCommentRepository; private final StudyMateQueryService studyMateQueryService; + private final RoomBoardRepository roomBoardRepository; @Override public RoomInfoPost findInfoPostById(Long PostId){ return roomInfoPostRepository.findById(PostId) @@ -44,9 +49,20 @@ public InfoPostComment findPostCommentById(Long troublePostId) { } @Override - public List getNextPosts(Long boardId, Long lastPostId, int size) { - Pageable pageable = PageRequest.of(0, size, Sort.by(Sort.Direction.DESC, "createdAt")); - return roomInfoPostRepository.findInfoPostSummariesForInfiniteScroll(boardId, lastPostId,pageable); + public List getNextPosts(Long roomId, Long lastPostId, int size) { + RoomBoard roomBoard = roomBoardRepository.findRoomBoardByRoomIdAndRoomPostType(roomId, RoomPostType.ROOM_INFORMATION_POST) + .orElseThrow(() -> new RestApiException(RoomPostErrorStatus._ROOM_BOARD_NOT_FOUND)); + + LocalDateTime lastCreatedAt; + if (lastPostId == 0) { + lastCreatedAt = LocalDateTime.now(); + } else { + lastCreatedAt = roomInfoPostRepository.findCreatedAtByIdOrEarliest(roomBoard.getId(), lastPostId) + .orElseThrow(() -> new RestApiException(RoomPostErrorStatus._POST_NOT_FOUND)); + } + + Pageable pageable = PageRequest.of(0, size, Sort.by(Sort.Direction.DESC, "createdAt", "id")); + return roomInfoPostRepository.findInfoPostSummariesForInfiniteScroll(roomBoard.getId(), lastCreatedAt, pageable); } diff --git a/src/main/java/gaji/service/domain/roomBoard/service/RoomPost/RoomPostCommandServiceImpl.java b/src/main/java/gaji/service/domain/roomBoard/service/RoomPost/RoomPostCommandServiceImpl.java index 3be8b1c0..111e23fe 100644 --- a/src/main/java/gaji/service/domain/roomBoard/service/RoomPost/RoomPostCommandServiceImpl.java +++ b/src/main/java/gaji/service/domain/roomBoard/service/RoomPost/RoomPostCommandServiceImpl.java @@ -51,7 +51,7 @@ public RoomPostResponseDto.toCreateRoomPostIdDTO createRoomPost(Long roomId, Lon StudyMate studyMate = studyMateQueryService.findByUserIdAndRoomId(user.getId(), roomId); // 스터디룸 게시판 확인 또는 생성 - RoomBoard roomBoard = roomBoardRepository.findByRoomId(roomId) + RoomBoard roomBoard = roomBoardRepository.findRoomBoardByRoomIdAndRoomPostType(roomId,RoomPostType.ROOM_POST) .orElseGet(() -> { RoomBoard newRoomBoard = RoomBoard.builder() .room(room) diff --git a/src/main/java/gaji/service/domain/roomBoard/service/RoomPost/RoomPostQueryService.java b/src/main/java/gaji/service/domain/roomBoard/service/RoomPost/RoomPostQueryService.java index 44757a3f..1432b643 100644 --- a/src/main/java/gaji/service/domain/roomBoard/service/RoomPost/RoomPostQueryService.java +++ b/src/main/java/gaji/service/domain/roomBoard/service/RoomPost/RoomPostQueryService.java @@ -13,7 +13,8 @@ public interface RoomPostQueryService { // List getPaginatedTroublePosts(Long boardId, int page, int size); - List getNextPosts(Long boardId, Long lastPostId, int size); + + List getNextPosts(Long roomId, Long lastPostId, int size); RoomPost findPostById(Long PostId); diff --git a/src/main/java/gaji/service/domain/roomBoard/service/RoomPost/RoomPostQueryServiceImpl.java b/src/main/java/gaji/service/domain/roomBoard/service/RoomPost/RoomPostQueryServiceImpl.java index d79669cf..8c901dfe 100644 --- a/src/main/java/gaji/service/domain/roomBoard/service/RoomPost/RoomPostQueryServiceImpl.java +++ b/src/main/java/gaji/service/domain/roomBoard/service/RoomPost/RoomPostQueryServiceImpl.java @@ -1,8 +1,11 @@ package gaji.service.domain.roomBoard.service.RoomPost; +import gaji.service.domain.enums.RoomPostType; import gaji.service.domain.roomBoard.code.RoomPostErrorStatus; +import gaji.service.domain.roomBoard.entity.RoomBoard; import gaji.service.domain.roomBoard.entity.RoomPost.PostComment; import gaji.service.domain.roomBoard.entity.RoomPost.RoomPost; +import gaji.service.domain.roomBoard.repository.RoomBoardRepository; import gaji.service.domain.roomBoard.repository.RoomPost.PostCommentRepository; import gaji.service.domain.roomBoard.repository.RoomPost.RoomPostQueryRepository; import gaji.service.domain.roomBoard.repository.RoomPost.RoomPostRepository; @@ -14,6 +17,7 @@ import org.springframework.data.domain.*; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -26,6 +30,7 @@ public class RoomPostQueryServiceImpl implements RoomPostQueryService { private final RoomPostRepository roomPostRepository; private final PostCommentRepository postCommentRepository; private final StudyMateQueryService studyMateQueryService; + private final RoomBoardRepository roomBoardRepository; @Override public List getTop3RecentPosts(Long roomId) { @@ -33,11 +38,21 @@ public List getTop3RecentPosts(Long roomId) { } @Override - public List getNextPosts(Long boardId, Long lastPostId, int size) { - Pageable pageable = PageRequest.of(0, size, Sort.by(Sort.Direction.DESC, "createdAt")); - return roomPostRepository.findPostSummariesForInfiniteScroll(boardId, lastPostId,pageable); + public List getNextPosts(Long roomId, Long lastPostId, int size) { + RoomBoard roomBoard = roomBoardRepository.findRoomBoardByRoomIdAndRoomPostType(roomId, RoomPostType.ROOM_POST) + .orElseThrow(() -> new RestApiException(RoomPostErrorStatus._ROOM_BOARD_NOT_FOUND)); + + LocalDateTime lastCreatedAt; + if (lastPostId == 0) { + lastCreatedAt = LocalDateTime.now(); + } else { + lastCreatedAt = roomPostRepository.findCreatedAtByIdOrEarliest(roomBoard.getId(), lastPostId) + .orElseThrow(() -> new RestApiException(RoomPostErrorStatus._POST_NOT_FOUND)); + } + + Pageable pageable = PageRequest.of(0, size, Sort.by(Sort.Direction.DESC, "createdAt", "id")); + return roomPostRepository.findPostSummariesForInfiniteScroll(roomBoard.getId(), lastCreatedAt, pageable); } - @Override public RoomPost findPostById(Long PostId){ return roomPostRepository.findById(PostId) diff --git a/src/main/java/gaji/service/domain/roomBoard/service/RoomTrouble/RoomTroublePostCommandServiceImpl.java b/src/main/java/gaji/service/domain/roomBoard/service/RoomTrouble/RoomTroublePostCommandServiceImpl.java index 5b3f74e1..44dbaace 100644 --- a/src/main/java/gaji/service/domain/roomBoard/service/RoomTrouble/RoomTroublePostCommandServiceImpl.java +++ b/src/main/java/gaji/service/domain/roomBoard/service/RoomTrouble/RoomTroublePostCommandServiceImpl.java @@ -68,7 +68,7 @@ public RoomPostResponseDto.toCreateRoomTroublePostIdDTO createRoomTroublePost(Lo StudyMate studyMate = studyMateQueryService.findByUserIdAndRoomId(user.getId(), roomId); // 스터디룸 게시판 확인 또는 생성 - RoomBoard roomBoard = roomBoardRepository.findByRoomId(roomId) + RoomBoard roomBoard = roomBoardRepository.findRoomBoardByRoomIdAndRoomPostType(roomId , RoomPostType.ROOM_TROUBLE_POST) .orElseGet(() -> { RoomBoard newRoomBoard = RoomBoard.builder() .room(room) diff --git a/src/main/java/gaji/service/domain/roomBoard/service/RoomTrouble/RoomTroublePostQueryServiceImpl.java b/src/main/java/gaji/service/domain/roomBoard/service/RoomTrouble/RoomTroublePostQueryServiceImpl.java index 148c93a3..66b0ffc6 100644 --- a/src/main/java/gaji/service/domain/roomBoard/service/RoomTrouble/RoomTroublePostQueryServiceImpl.java +++ b/src/main/java/gaji/service/domain/roomBoard/service/RoomTrouble/RoomTroublePostQueryServiceImpl.java @@ -1,8 +1,11 @@ package gaji.service.domain.roomBoard.service.RoomTrouble; +import gaji.service.domain.enums.RoomPostType; import gaji.service.domain.roomBoard.code.RoomPostErrorStatus; +import gaji.service.domain.roomBoard.entity.RoomBoard; import gaji.service.domain.roomBoard.entity.RoomTrouble.RoomTroublePost; import gaji.service.domain.roomBoard.entity.RoomTrouble.TroublePostComment; +import gaji.service.domain.roomBoard.repository.RoomBoardRepository; import gaji.service.domain.roomBoard.repository.RoomTrouble.RoomTroublePostRepository; import gaji.service.domain.roomBoard.repository.RoomTrouble.TroublePostCommentRepository; import gaji.service.domain.roomBoard.web.dto.RoomPostResponseDto; @@ -13,6 +16,7 @@ import org.springframework.data.domain.*; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -24,6 +28,7 @@ public class RoomTroublePostQueryServiceImpl implements RoomTroublePostQueryServ private final TroublePostCommentRepository troublePostCommentRepository; private final RoomTroublePostRepository roomTroublePostRepository; private final StudyMateQueryService studyMateQueryService; + private final RoomBoardRepository roomBoardRepository; @Override public TroublePostComment findCommentByCommentId(Long commentId){ @@ -38,9 +43,20 @@ public TroublePostComment findTroublePostCommentById(Long troublePostId) { } @Override - public List getNextTroublePosts(Long boardId, Long lastPostId, int size) { - Pageable pageable = PageRequest.of(0, size, Sort.by(Sort.Direction.DESC, "createdAt")); - return roomTroublePostRepository.findTroublePostSummariesForInfiniteScroll(boardId, lastPostId,pageable); + public List getNextTroublePosts(Long roomId, Long lastPostId, int size) { + RoomBoard roomBoard = roomBoardRepository.findRoomBoardByRoomIdAndRoomPostType(roomId, RoomPostType.ROOM_TROUBLE_POST) + .orElseThrow(() -> new RestApiException(RoomPostErrorStatus._ROOM_BOARD_NOT_FOUND)); + + LocalDateTime lastCreatedAt; + if (lastPostId == 0) { + lastCreatedAt = LocalDateTime.now(); + } else { + lastCreatedAt = roomTroublePostRepository.findCreatedAtByIdOrEarliest(roomBoard.getId(), lastPostId) + .orElseThrow(() -> new RestApiException(RoomPostErrorStatus._POST_NOT_FOUND)); + } + + Pageable pageable = PageRequest.of(0, size, Sort.by(Sort.Direction.DESC, "createdAt", "id")); + return roomTroublePostRepository.findTroublePostSummariesForInfiniteScroll(roomBoard.getId(), lastCreatedAt, pageable); } diff --git a/src/main/java/gaji/service/domain/roomBoard/web/controller/RoomInfoPostController.java b/src/main/java/gaji/service/domain/roomBoard/web/controller/RoomInfoPostController.java index ad78f9b1..eeaac3be 100644 --- a/src/main/java/gaji/service/domain/roomBoard/web/controller/RoomInfoPostController.java +++ b/src/main/java/gaji/service/domain/roomBoard/web/controller/RoomInfoPostController.java @@ -146,17 +146,15 @@ public BaseResponse removeBookmark( return BaseResponse.onSuccess( "북마크가 성공적으로 삭제되었습니다."); } - @GetMapping("/{boardId}/info") + @GetMapping("/{roomId}/info/list") @Operation(summary = "게시글 무한 스크롤 조회", description = "게시글을 무한 스크롤 방식으로 조회합니다.") @ApiResponse(responseCode = "200", description = "조회 성공") - public BaseResponse> getNextTroublePosts( - @PathVariable @Parameter(description = "게시판 ID") Long boardId, + public BaseResponse> getNextInfoPosts( + @PathVariable @Parameter(description = "스터디룸 ID") Long roomId, @RequestParam @Parameter(description = "마지막으로 로드된 게시글 ID") Long lastPostId, @RequestParam(defaultValue = "10") @Parameter(description = "조회할 게시글 수") int size) { - List posts = - roomInfoPostQueryService.getNextPosts(boardId, lastPostId, size); - + roomInfoPostQueryService.getNextPosts(roomId, lastPostId, size); return BaseResponse.onSuccess(posts); } diff --git a/src/main/java/gaji/service/domain/roomBoard/web/controller/RoomPostController.java b/src/main/java/gaji/service/domain/roomBoard/web/controller/RoomPostController.java index dc43b7a1..94da029f 100644 --- a/src/main/java/gaji/service/domain/roomBoard/web/controller/RoomPostController.java +++ b/src/main/java/gaji/service/domain/roomBoard/web/controller/RoomPostController.java @@ -147,20 +147,31 @@ public BaseResponse removeBookmark( return BaseResponse.onSuccess( "북마크가 성공적으로 삭제되었습니다."); } - @GetMapping("/{boardId}/post") +// @GetMapping("/{roomId}/post/list") +// @Operation(summary = "게시글 무한 스크롤 조회", description = "게시글을 무한 스크롤 방식으로 조회합니다.") +// @ApiResponse(responseCode = "200", description = "조회 성공") +// public BaseResponse> getNextPosts( +// @PathVariable @Parameter(description = "스터디룸 ID") Long roomId, +// @RequestParam @Parameter(description = "마지막으로 로드된 게시글 ID") Long lastPostId, +// @RequestParam(defaultValue = "10") @Parameter(description = "조회할 게시글 수") int size) { +// +// List posts = +// roomPostQueryService.getNextPosts(roomId, lastPostId, size); +// +// return BaseResponse.onSuccess(posts); +// } + + @GetMapping("/{roomId}/post/list") @Operation(summary = "게시글 무한 스크롤 조회", description = "게시글을 무한 스크롤 방식으로 조회합니다.") @ApiResponse(responseCode = "200", description = "조회 성공") public BaseResponse> getNextPosts( - @PathVariable @Parameter(description = "게시판 ID") Long boardId, + @PathVariable @Parameter(description = "스터디룸 ID") Long roomId, @RequestParam @Parameter(description = "마지막으로 로드된 게시글 ID") Long lastPostId, @RequestParam(defaultValue = "10") @Parameter(description = "조회할 게시글 수") int size) { - List posts = - roomPostQueryService.getNextPosts(boardId, lastPostId, size); - + roomPostQueryService.getNextPosts(roomId, lastPostId, size); return BaseResponse.onSuccess(posts); } - @PostMapping("/post/comments/{commentId}/replies") @Operation(summary = "게시글 댓글의 답글 작성 API") public BaseResponse addReply( diff --git a/src/main/java/gaji/service/domain/roomBoard/web/controller/RoomTroublePostController.java b/src/main/java/gaji/service/domain/roomBoard/web/controller/RoomTroublePostController.java index d1f68787..4d565907 100644 --- a/src/main/java/gaji/service/domain/roomBoard/web/controller/RoomTroublePostController.java +++ b/src/main/java/gaji/service/domain/roomBoard/web/controller/RoomTroublePostController.java @@ -159,17 +159,15 @@ public BaseResponse addReply( return BaseResponse.onSuccess(RoomPostConverter.toWriteCommentDto(replyComment)); } - @GetMapping("/{boardId}/trouble") + @GetMapping("/{roomId}/trouble/list") @Operation(summary = "트러블 슈팅 게시글 무한 스크롤 조회", description = "트러블 슈팅 게시글을 무한 스크롤 방식으로 조회합니다.") @ApiResponse(responseCode = "200", description = "조회 성공") public BaseResponse> getNextTroublePosts( - @PathVariable @Parameter(description = "게시판 ID") Long boardId, + @PathVariable @Parameter(description = "게시판 ID") Long roomId, @RequestParam @Parameter(description = "마지막으로 로드된 게시글 ID") Long lastPostId, @RequestParam(defaultValue = "10") @Parameter(description = "조회할 게시글 수") int size) { - - List posts = - roomTroublePostQueryService.getNextTroublePosts(boardId, lastPostId, size); - + List posts; + posts = roomTroublePostQueryService.getNextTroublePosts(roomId, lastPostId, size); return BaseResponse.onSuccess(posts); } diff --git a/src/main/java/gaji/service/domain/studyMate/code/StudyMateErrorStatus.java b/src/main/java/gaji/service/domain/studyMate/code/StudyMateErrorStatus.java index 5fde3b74..da576180 100644 --- a/src/main/java/gaji/service/domain/studyMate/code/StudyMateErrorStatus.java +++ b/src/main/java/gaji/service/domain/studyMate/code/StudyMateErrorStatus.java @@ -11,8 +11,11 @@ @AllArgsConstructor public enum StudyMateErrorStatus implements BaseErrorCodeInterface { // 스터디룸 게시판 - _USER_NOT_IN_STUDYROOM(HttpStatus.BAD_REQUEST, "StudyMateError_4001", "회원이 해당 스터디룸에 참여하고 있지 않습니다."); - + _USER_NOT_IN_STUDYROOM(HttpStatus.BAD_REQUEST, "StudyMateError_4001", "회원이 해당 스터디룸에 참여하고 있지 않습니다."), + _USER_ALREADY_JOIN(HttpStatus.BAD_REQUEST, "StudyMateError_4002", "이미 참여중인 회원입니다."), + _ONLY_LEADER_POSSIBLE(HttpStatus.BAD_REQUEST, "StudyMateError_4003", "스터디 리더만 가능한 작업입니다."), + _LEADER_IMPOSSIBLE_LEAVE(HttpStatus.BAD_REQUEST, "StudyMateError_4004", "스터디 리더는 나갈 수 없습니다."), + ; private final HttpStatus httpStatus; diff --git a/src/main/java/gaji/service/domain/studyMate/entity/WeeklyUserProgress.java b/src/main/java/gaji/service/domain/studyMate/entity/WeeklyUserProgress.java index 2ebe8c36..1c1fa88c 100644 --- a/src/main/java/gaji/service/domain/studyMate/entity/WeeklyUserProgress.java +++ b/src/main/java/gaji/service/domain/studyMate/entity/WeeklyUserProgress.java @@ -25,12 +25,27 @@ public class WeeklyUserProgress { private Double progressPercentage; - // 총 과제 수 private Integer totalAssignments; - // 완료한 과제 수 private Integer completedAssignments; + public static WeeklyUserProgress createInitialProgress(User user, RoomEvent roomEvent, int totalAssignments) { + return WeeklyUserProgress.builder() + .user(user) + .roomEvent(roomEvent) + .progressPercentage(0.0) + .totalAssignments(totalAssignments) + .completedAssignments(0) + .build(); + } + + public void updateProgress(int completedAssignments) { + this.completedAssignments = completedAssignments; + this.progressPercentage = totalAssignments > 0 + ? ((double) completedAssignments / totalAssignments) * 100 + : 0.0; + } + public static WeeklyUserProgress createEmpty() { return new WeeklyUserProgress(); } diff --git a/src/main/java/gaji/service/domain/studyMate/repository/StudyMateRepository.java b/src/main/java/gaji/service/domain/studyMate/repository/StudyMateRepository.java index 0a20449d..934b19f8 100644 --- a/src/main/java/gaji/service/domain/studyMate/repository/StudyMateRepository.java +++ b/src/main/java/gaji/service/domain/studyMate/repository/StudyMateRepository.java @@ -2,10 +2,10 @@ import gaji.service.domain.room.entity.Room; import gaji.service.domain.studyMate.entity.StudyMate; +import gaji.service.domain.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import javax.swing.text.html.Option; import java.util.List; import java.util.Optional; @@ -14,6 +14,9 @@ public interface StudyMateRepository extends JpaRepository { Optional findByUserIdAndRoomId(Long userId, Long roomId); List findByRoom(Room room); - StudyMate findByRoomIdAndUserId(Long roomId, Long userId); + + boolean existsByUserAndRoom(User user, Room room); + + void deleteByUserAndRoom(User user, Room room); } \ No newline at end of file diff --git a/src/main/java/gaji/service/domain/studyMate/service/StudyMateCommandService.java b/src/main/java/gaji/service/domain/studyMate/service/StudyMateCommandService.java new file mode 100644 index 00000000..f63faed5 --- /dev/null +++ b/src/main/java/gaji/service/domain/studyMate/service/StudyMateCommandService.java @@ -0,0 +1,11 @@ +package gaji.service.domain.studyMate.service; + +import gaji.service.domain.room.entity.Room; +import gaji.service.domain.studyMate.entity.StudyMate; +import gaji.service.domain.user.entity.User; + +public interface StudyMateCommandService { + void saveStudyMate(StudyMate studyMate); + + void deleteByUserAndRoom(User user, Room room); +} diff --git a/src/main/java/gaji/service/domain/studyMate/service/StudyMateCommandServiceImpl.java b/src/main/java/gaji/service/domain/studyMate/service/StudyMateCommandServiceImpl.java new file mode 100644 index 00000000..07c0e374 --- /dev/null +++ b/src/main/java/gaji/service/domain/studyMate/service/StudyMateCommandServiceImpl.java @@ -0,0 +1,27 @@ +package gaji.service.domain.studyMate.service; + +import gaji.service.domain.room.entity.Room; +import gaji.service.domain.studyMate.entity.StudyMate; +import gaji.service.domain.studyMate.repository.StudyMateRepository; +import gaji.service.domain.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class StudyMateCommandServiceImpl implements StudyMateCommandService{ + private final StudyMateRepository studyMateRepository; + + @Override + @Transactional + public void saveStudyMate(StudyMate studyMate) { + studyMateRepository.save(studyMate); + } + + @Override + @Transactional + public void deleteByUserAndRoom(User user, Room room) { + studyMateRepository.deleteByUserAndRoom(user, room); + } +} diff --git a/src/main/java/gaji/service/domain/studyMate/service/StudyMateQueryService.java b/src/main/java/gaji/service/domain/studyMate/service/StudyMateQueryService.java index 89372289..e43ba8b5 100644 --- a/src/main/java/gaji/service/domain/studyMate/service/StudyMateQueryService.java +++ b/src/main/java/gaji/service/domain/studyMate/service/StudyMateQueryService.java @@ -1,9 +1,15 @@ package gaji.service.domain.studyMate.service; +import gaji.service.domain.room.entity.Room; import gaji.service.domain.studyMate.entity.StudyMate; +import gaji.service.domain.user.entity.User; public interface StudyMateQueryService { StudyMate findByUserIdAndRoomId(Long id, Long roomId); StudyMate findById(Long studyMateId); + + boolean existsByUserAndRoom(User user, Room room); + + boolean checkLeader(User user, Room room); } diff --git a/src/main/java/gaji/service/domain/studyMate/service/StudyMateQueryServiceImpl.java b/src/main/java/gaji/service/domain/studyMate/service/StudyMateQueryServiceImpl.java index 155adc7e..40fc8605 100644 --- a/src/main/java/gaji/service/domain/studyMate/service/StudyMateQueryServiceImpl.java +++ b/src/main/java/gaji/service/domain/studyMate/service/StudyMateQueryServiceImpl.java @@ -1,8 +1,11 @@ package gaji.service.domain.studyMate.service; +import gaji.service.domain.enums.Role; +import gaji.service.domain.room.entity.Room; import gaji.service.domain.studyMate.code.StudyMateErrorStatus; import gaji.service.domain.studyMate.entity.StudyMate; import gaji.service.domain.studyMate.repository.StudyMateRepository; +import gaji.service.domain.user.entity.User; import gaji.service.global.exception.RestApiException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -26,4 +29,15 @@ public StudyMate findById(Long studyMateId){ .orElseThrow(() -> new RestApiException(StudyMateErrorStatus._USER_NOT_IN_STUDYROOM)); } + @Override + public boolean existsByUserAndRoom(User user, Room room) { + return studyMateRepository.existsByUserAndRoom(user, room); + } + + @Override + public boolean checkLeader(User user, Room room) { + StudyMate studyMate = findByUserIdAndRoomId(user.getId(), room.getId()); + return studyMate.getRole() == Role.READER; + } + } diff --git a/src/main/java/gaji/service/jwt/service/CustomSuccessHandler.java b/src/main/java/gaji/service/jwt/service/CustomSuccessHandler.java index 7772d92f..27617b8f 100644 --- a/src/main/java/gaji/service/jwt/service/CustomSuccessHandler.java +++ b/src/main/java/gaji/service/jwt/service/CustomSuccessHandler.java @@ -60,8 +60,6 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo addRefreshEntity(usernameId, refreshToken, 86400000000L); } - - // Refresh 토큰을 HttpOnly 쿠키로 설정 Cookie refreshTokenCookie = new Cookie("refresh_token", refreshToken); refreshTokenCookie.setHttpOnly(true); @@ -72,7 +70,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo // 1. 헤더로 보낼 경우 -// response.setHeader("Authorization", "Bearer " + accessToken); + response.setHeader("Authorization", "Bearer " + accessToken); @@ -90,13 +88,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo log.info("refreshToken = {}", refreshToken); - String finalRedirectionUrl; - if (customUserDetails.isNewUser()) { - finalRedirectionUrl = this.nicknameRedirectionUrl; + String finalRedirectionUrl = customUserDetails.isNewUser() ? this.nicknameRedirectionUrl : this.redirectionUrl; - } else { - finalRedirectionUrl = this.redirectionUrl; - } // 리다이렉션 URL 생성