From aecb65d93e6e6d223e11c4e81842d74d4a57c454 Mon Sep 17 00:00:00 2001 From: DongHoon Lee <125895298+hoonyworld@users.noreply.github.com> Date: Sat, 21 Sep 2024 20:24:41 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=ED=94=BC=EB=93=9C(post)=20?= =?UTF-8?q?=EC=B0=A8=EB=8B=A8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=EC=B6=95=20?= =?UTF-8?q?(#387)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(PostDetailBaseDto): 불필요한 import문 삭제 * feat(PostDetailResponseDto): 차단된 유저의 게시물인지 여부를 나타내는 필드 추가 * feat(UserV2Service): id를 통해 User 객체를 가져오는 메서드 선언 * feat(UserV2ServiceImpl): id를 통해 User 객체를 가져오는 메서드 구현 * refactor: 매개변수로 userId가 아닌 user 객체를 받도록 변경 * feat(PostSearchRepositoryImpl): 차단된 유저의 게시물 여부를 확인하는 서브쿼리 추가 * test(PostRepositoryTest): 테스트 임시 비활성화 * feat(PostDetailWithBlockStatusResponseDto): 게시글 객체 + 차단된 유저의 게시물 여부를 나타내는 응답 DTO 생성 * refactor(PostV2GetPostsResponseDto): DTO 객체를 PostDetailResponseDto에서 PostDetailWithBlockStatusResponseDto로 수정 * chore(PostDetailBaseDto): description 상세화 * refactor(PostDetailResponseDto): isBlockedPost 필드 삭제 및 description 상세화 * refactor(PostSearchRepositoryImpl): 차단된 유저의 게시물 여부 확인 서브쿼리 삭제 * feat(MemberBlockSearchRepository): 특정 사용자가 다른 사용자를 차단했는지 여부를 확인하는 메서드 선언 * refactor(MemberBlockRepository): 커스텀 쿼리를 메서드를 구현하기 위한 MemberBlockSearchRepository상속 * feat(MemberBlockRepositoryImpl): 회원 차단 여부 확인을 위한 쿼리 메서드 추가 * feat(MemberBlockService): 회원 차단 여부 확인을 위한 MemberBlockService 인터페이스 정의 * feat(MemberBlockServiceImpl): 회원 차단 여부 확인메서드 구현 * refactor(PostV2ServiceImpl):회원 차단 여부를 DTO로 넘겨주도록 변경 * feat(JpaAuditingConfig): multi-datasource 환경을 위한 JPAQueryFactory 설정 추가 - primaryEntityManager와 playgroundEntityManager를 위한 JPAQueryFactory 빈 등록 - QueryDSL을 사용하여 다양한 데이터 소스에 대한 쿼리 수행 가능 * fix(MemberBlockRepositoryImpl): playgroundQueryFactory를 주입하여 다중 데이터소스 지원 * refactor(MemberBlockSearchRepository): 차단자가 특정 차단된 사용자 목록에 대해 각각의 차단 상태를 확인하는 메서드로 변경 * refactor(MemberBlockRepositoryImpl): 단일 쿼리로 차단자가 특정 차단된 사용자 목록에 대해 각각의 차단 상태를 확인하는 메서드 구현 * refactor(MemberBlockService): 차단자가 특정 차단된 사용자 목록에 대해 각각의 차단 상태 정보를 얻을 수 있는 메서드 선언 * refactor(MemberBlockServiceImpl): 차단자가 특정 차단된 사용자 목록에 대해 각각의 차단 상태 정보를 얻을 수 있는 메서드 구현 * rename(MemberBlockSearchRepositoryImpl): MemberBlockRepositoryImpl에서 MemberBlockSearchRepositoryImpl로 클래스 이름 변경 * refactor: 매개변수 이름 변경 * refactor(PostV2ServiceImpl): 조회된 게시물 작성자의 orgId 리스트를 수집하여 차단 여부를 확인하는 로직 최적화 * rename(PostDetailWithBlockStatusResponseDto): 응답 필드명 변경 * chore: naver 코드 포맷터 적 * refactor: 매개변수를 userId로 받도록 변경 * refactor: osiv 관련 코드리뷰 반 * test: test yml 수정 * chore(config): test 설정 추가, 테스트 프로필일 경우 schema.sql 실행 * test(config): 테스트컨테이너 사용하도록 코드 전면 수정 * chore(MemberBlock): [중요] querydsl -> spring data jpa 변경으로 인한 임시 수정 * refactor: 역할에 맞게 서비스 레이어에서 Map으로 변환하도록 리팩토링 * test(PostRepositoryTest): 주석 해제 * delete: querydsl 대신 spring data jpa를 사용하기 위해 삭제 - spring data jpa의 메서드 네이밍이 복잡하지 않으며 성능차이도 비슷하기 때문에 코드를 줄이는 방향으로 결정 * feat(MemberBlockRepository): 차단자의 orgId로 차단된 모든 유저의 정보를 조회하는 메서드 추가 * refactor(MemberBlockServiceImpl): spring data jpa를 이용해 차단된 유저의 정보를 Map으로 반환하도록 변경 * chore(application-test.yml): 테스트 컨테이너 jdbc-url 변경 --------- Co-authored-by: mikekks --- main/build.gradle | 1 + .../common/config/CrewDatabaseConfig.java | 39 ++- .../main/common/config/JpaAuditingConfig.java | 31 ++- .../config/PlaygroundDataSourceConfig.java | 11 +- .../entity/post/PostSearchRepository.java | 4 +- .../entity/post/PostSearchRepositoryImpl.java | 109 ++++---- .../member_block/MemberBlockRepository.java | 5 +- .../service/MemberBlockService.java | 8 + .../service/MemberBlockServiceImpl.java | 27 ++ .../makers/crew/main/post/v2/PostV2Api.java | 128 ++++------ .../crew/main/post/v2/PostV2Controller.java | 150 +++++------ .../v2/dto/response/PostDetailBaseDto.java | 126 ++++----- .../dto/response/PostDetailResponseDto.java | 123 ++++----- .../PostDetailWithBlockStatusResponseDto.java | 20 ++ .../response/PostV2GetPostsResponseDto.java | 5 +- .../main/post/v2/service/PostV2Service.java | 18 +- .../post/v2/service/PostV2ServiceImpl.java | 103 ++++---- .../main/user/v2/service/UserV2Service.java | 3 +- .../user/v2/service/UserV2ServiceImpl.java | 31 +-- main/src/main/resources/application-test.yml | 20 +- main/src/main/resources/schema.sql | 201 +++++++++++++++ .../main/entity/user/UserRepositoryTest.java | 19 +- .../v2/repository/ApplyRepositoryTest.java | 8 +- .../v2/repository/PostRepositoryTest.java | 239 +++++++++--------- .../sql/apply-repository-test-data.sql | 26 -- .../test/resources/sql/delete-all-data.sql | 2 +- .../sql/post-repository-test-data.sql | 26 -- 27 files changed, 852 insertions(+), 631 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailWithBlockStatusResponseDto.java create mode 100644 main/src/main/resources/schema.sql diff --git a/main/build.gradle b/main/build.gradle index ed190c18..aa80ef21 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -36,6 +36,7 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation group: 'org.postgresql', name: 'postgresql', version: '42.6.0' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.postgresql:postgresql:42.3.0' // jsonb 타입 핸들링 위함 implementation 'io.hypersistence:hypersistence-utils-hibernate-62:3.6.0' diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/CrewDatabaseConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/CrewDatabaseConfig.java index 27bfa98b..fbfb0886 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/CrewDatabaseConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/CrewDatabaseConfig.java @@ -1,5 +1,6 @@ package org.sopt.makers.crew.main.common.config; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -7,19 +8,25 @@ import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.CommandLineRunner; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; +import jakarta.persistence.EntityManagerFactory; + @Configuration @EnableTransactionManagement @EnableJpaRepositories( @@ -27,7 +34,7 @@ entityManagerFactoryRef = "primaryEntityManagerFactory", // EntityManager의 이름 transactionManagerRef = "primaryTransactionManager" // 트랜잭션 매니저의 이름 ) -@Profile({"local", "dev", "prod"}) +@Profile({"local", "dev", "prod", "test"}) public class CrewDatabaseConfig { @Bean @@ -52,10 +59,13 @@ public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory() { Map properties = new HashMap<>(); properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"); - properties.put("hibernate.hbm2ddl.auto", "validate"); properties.put("hibernate.format_sql", true); properties.put("hibernate.physical_naming_strategy", "org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy"); - properties.put("hibernate.globally_quoted_identifiers", true); + + String[] activeProfiles = {"local", "dev", "prod"}; + if (Arrays.stream(activeProfiles).anyMatch(profile -> profile.equals(System.getProperty("spring.profiles.active")))) { + properties.put("hibernate.hbm2ddl.auto", "validate"); + } em.setJpaPropertyMap(properties); return em; @@ -68,4 +78,27 @@ public PlatformTransactionManager primaryTransactionManager( ) { return new JpaTransactionManager(Objects.requireNonNull(localContainerEntityManagerFactoryBean.getObject())); } + + private final ResourceLoader resourceLoader; + + public CrewDatabaseConfig(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + @Bean + @Profile("test") + public CommandLineRunner init(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) { + return args -> executeSchemaSql(); + } + + private void executeSchemaSql() { + Resource resource = resourceLoader.getResource("classpath:schema.sql"); + ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(); + databasePopulator.addScript(resource); + try { + databasePopulator.populate(primaryDatasourceProperties().getConnection()); + } catch (Exception e) { + System.out.println(e); + } + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/JpaAuditingConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/JpaAuditingConfig.java index ef4a1927..b4b92297 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/JpaAuditingConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/JpaAuditingConfig.java @@ -1,21 +1,34 @@ package org.sopt.makers.crew.main.common.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + import com.querydsl.jpa.JPQLTemplates; import com.querydsl.jpa.impl.JPAQueryFactory; + import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @EnableJpaAuditing @Configuration public class JpaAuditingConfig { - @PersistenceContext - private EntityManager entityManager; - @Bean - public JPAQueryFactory queryFactory() { - return new JPAQueryFactory(JPQLTemplates.DEFAULT, entityManager); - } + @PersistenceContext(unitName = "primaryEntityManagerFactory") + private EntityManager primaryEntityManager; + + @PersistenceContext(unitName = "secondEntityManagerFactory") + private EntityManager playgroundEntityManager; + + @Bean(name = "primaryQueryFactory") + @Primary + public JPAQueryFactory primaryQueryFactory() { + return new JPAQueryFactory(JPQLTemplates.DEFAULT, primaryEntityManager); + } + + @Bean(name = "playgroundQueryFactory") + public JPAQueryFactory playgroundQueryFactory() { + return new JPAQueryFactory(JPQLTemplates.DEFAULT, playgroundEntityManager); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/PlaygroundDataSourceConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/PlaygroundDataSourceConfig.java index 5cde2b6c..5c4c255f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/PlaygroundDataSourceConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/PlaygroundDataSourceConfig.java @@ -1,5 +1,6 @@ package org.sopt.makers.crew.main.common.config; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -26,7 +27,7 @@ entityManagerFactoryRef = "secondEntityManagerFactory", transactionManagerRef = "secondTransactionManager" ) -@Profile({"local", "dev", "prod"}) +@Profile({"local", "dev", "prod", "test"}) public class PlaygroundDataSourceConfig { @Bean @ConfigurationProperties("spring.playground-datasource") @@ -48,10 +49,14 @@ public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory() { Map properties = new HashMap<>(); properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"); - properties.put("hibernate.hbm2ddl.auto", "validate"); properties.put("hibernate.format_sql", true); properties.put("hibernate.physical_naming_strategy", "org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy"); - properties.put("hibernate.globally_quoted_identifiers", true); + + String[] activeProfiles = {"local", "dev", "prod"}; + if (Arrays.stream(activeProfiles).anyMatch(profile -> profile.equals(System.getProperty("spring.profiles.active")))) { + properties.put("hibernate.hbm2ddl.auto", "validate"); + } + em.setJpaPropertyMap(properties); return em; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java index df3f5db2..cfb5d811 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java @@ -7,7 +7,7 @@ import org.springframework.data.domain.Pageable; public interface PostSearchRepository { - Page findPostList(PostGetPostsCommand queryCommand, Pageable pageable, Integer userId); + Page findPostList(PostGetPostsCommand queryCommand, Pageable pageable, Integer userId); - PostDetailBaseDto findPost(Integer userId, Integer postId); + PostDetailBaseDto findPost(Integer userId, Integer postId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java index e6423581..3154aaaf 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java @@ -1,18 +1,11 @@ package org.sopt.makers.crew.main.entity.post; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_POST; -import static org.sopt.makers.crew.main.entity.comment.QComment.comment; -import static org.sopt.makers.crew.main.entity.like.QLike.like; -import static org.sopt.makers.crew.main.entity.meeting.QMeeting.meeting; -import static org.sopt.makers.crew.main.entity.post.QPost.post; -import static org.sopt.makers.crew.main.entity.user.QUser.user; - -import com.querydsl.core.group.GroupBy; -import com.querydsl.core.types.ExpressionUtils; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.jpa.JPAExpressions; -import com.querydsl.jpa.impl.JPAQuery; -import com.querydsl.jpa.impl.JPAQueryFactory; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; +import static org.sopt.makers.crew.main.entity.comment.QComment.*; +import static org.sopt.makers.crew.main.entity.like.QLike.*; +import static org.sopt.makers.crew.main.entity.meeting.QMeeting.*; +import static org.sopt.makers.crew.main.entity.post.QPost.*; +import static org.sopt.makers.crew.main.entity.user.QUser.*; import java.util.ArrayList; import java.util.Collections; @@ -20,8 +13,6 @@ import java.util.Map; import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; - import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.response.CommenterThumbnails; @@ -36,6 +27,15 @@ import org.springframework.data.support.PageableExecutionUtils; import org.springframework.stereotype.Repository; +import com.querydsl.core.group.GroupBy; +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + @Repository @RequiredArgsConstructor public class PostSearchRepositoryImpl implements PostSearchRepository { @@ -45,31 +45,24 @@ public class PostSearchRepositoryImpl implements PostSearchRepository { public Page findPostList(PostGetPostsCommand queryCommand, Pageable pageable, Integer userId) { Integer meetingId = queryCommand.getMeetingId().orElse(null); - List content = getContentList(pageable, meetingId, userId); + JPAQuery countQuery = getCount(meetingId); - return PageableExecutionUtils.getPage(content, - PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()), countQuery::fetchFirst); + return PageableExecutionUtils.getPage(content, PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()), + countQuery::fetchFirst); } @Override public PostDetailBaseDto findPost(Integer userId, Integer postId) { - PostDetailBaseDto postDetail = queryFactory - .select(new QPostDetailBaseDto( - post.id, post.title, post.contents, post.createdDate, post.images, - new QPostWriterInfoDto(post.user.id, post.user.orgId, post.user.name, post.user.profileImage), - post.likeCount, - ExpressionUtils.as( - JPAExpressions.selectFrom(like) - .where(like.postId.eq(post.id).and(like.userId.eq(userId))) - .exists() - , "isLiked" - ), - post.viewCount, post.commentCount, - new QPostMeetingDto(post.meeting.id, post.meeting.title, post.meeting.category, post.meeting.imageURL, - post.meeting.desc) - )) + PostDetailBaseDto postDetail = queryFactory.select( + new QPostDetailBaseDto(post.id, post.title, post.contents, post.createdDate, post.images, + new QPostWriterInfoDto(post.user.id, post.user.orgId, post.user.name, post.user.profileImage), + post.likeCount, ExpressionUtils.as( + JPAExpressions.selectFrom(like).where(like.postId.eq(post.id).and(like.userId.eq(userId))).exists(), + "isLiked"), post.viewCount, post.commentCount, + new QPostMeetingDto(post.meeting.id, post.meeting.title, post.meeting.category, post.meeting.imageURL, + post.meeting.desc))) .from(post) .innerJoin(post.meeting, meeting) .innerJoin(post.user, user) @@ -83,25 +76,15 @@ public PostDetailBaseDto findPost(Integer userId, Integer postId) { return postDetail; } - private List getContentList(Pageable pageable, Integer meetingId, - Integer userId) { - List responseDtos = new ArrayList<>(); - - List postDetailList = queryFactory - .select(new QPostDetailBaseDto( - post.id, post.title, post.contents, post.createdDate, post.images, - new QPostWriterInfoDto(post.user.id, post.user.orgId, post.user.name, post.user.profileImage), - post.likeCount, - ExpressionUtils.as( - JPAExpressions.selectFrom(like) - .where(like.postId.eq(post.id).and(like.userId.eq(userId))) - .exists() - , "isLiked" - ), - post.viewCount, post.commentCount, - new QPostMeetingDto(post.meeting.id, post.meeting.title, post.meeting.category, post.meeting.imageURL, - post.meeting.desc) - )) + private List getContentList(Pageable pageable, Integer meetingId, Integer userId) { + List postDetails = queryFactory.select( + new QPostDetailBaseDto(post.id, post.title, post.contents, post.createdDate, post.images, + new QPostWriterInfoDto(post.user.id, post.user.orgId, post.user.name, post.user.profileImage), + post.likeCount, ExpressionUtils.as( + JPAExpressions.selectFrom(like).where(like.postId.eq(post.id).and(like.userId.eq(userId))).exists(), + "isLiked"), post.viewCount, post.commentCount, + new QPostMeetingDto(post.meeting.id, post.meeting.title, post.meeting.category, post.meeting.imageURL, + post.meeting.desc))) .from(post) .innerJoin(post.user, user) .innerJoin(post.meeting, meeting) @@ -112,13 +95,11 @@ private List getContentList(Pageable pageable, Integer me .fetch(); // 모든 게시글 ID를 추출 - List postIds = postDetailList.stream() - .map(PostDetailBaseDto::getId) - .collect(Collectors.toList()); + List postIds = postDetails.stream().map(PostDetailBaseDto::getId).collect(Collectors.toList()); - // 게시글 ID 리스트를 사용하여 한 번에 모든 댓글 작성자의 프로필 이미지를 조회 - Map> commenterThumbnailsMap = queryFactory - .select(comment.post.id, comment.user.profileImage) + // 게시글 ID 리스트를 사용하여 모든 댓글 작성자의 프로필 이미지를 조회 + Map> commenterThumbnailsMap = queryFactory.select(comment.post.id, + comment.user.profileImage) .from(comment) .where(comment.post.id.in(postIds)) .groupBy(comment.post.id, comment.user.id, comment.user.profileImage) @@ -127,10 +108,10 @@ private List getContentList(Pageable pageable, Integer me .transform(GroupBy.groupBy(comment.post.id).as(GroupBy.list(comment.user.profileImage))); // 각 게시글별로 댓글 작성자의 프로필 이미지 리스트를 설정 - for (PostDetailBaseDto postDetail : postDetailList) { + List responseDtos = new ArrayList<>(); + for (PostDetailBaseDto postDetail : postDetails) { CommenterThumbnails commenterThumbnails = new CommenterThumbnails( - commenterThumbnailsMap.getOrDefault(postDetail.getId(), - Collections.emptyList())); + commenterThumbnailsMap.getOrDefault(postDetail.getId(), Collections.emptyList())); responseDtos.add(PostDetailResponseDto.of(postDetail, commenterThumbnails)); } @@ -138,14 +119,10 @@ private List getContentList(Pageable pageable, Integer me } private JPAQuery getCount(Integer meetingId) { - return queryFactory - .select(post.count()) - .from(post) - .where(meetingIdEq(meetingId)); + return queryFactory.select(post.count()).from(post).where(meetingIdEq(meetingId)); } private BooleanExpression meetingIdEq(Integer meetingId) { return meetingId == null ? null : post.meetingId.eq(meetingId); } - } diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/entity/member_block/MemberBlockRepository.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/entity/member_block/MemberBlockRepository.java index f7e9089f..628aa4c5 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/external/playground/entity/member_block/MemberBlockRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/entity/member_block/MemberBlockRepository.java @@ -1,6 +1,9 @@ package org.sopt.makers.crew.main.external.playground.entity.member_block; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; public interface MemberBlockRepository extends JpaRepository { -} + List findAllByBlockerAndIsBlockedTrue(Long orgId); +} \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java new file mode 100644 index 00000000..d125cf78 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java @@ -0,0 +1,8 @@ +package org.sopt.makers.crew.main.external.playground.service; + +import java.util.List; +import java.util.Map; + +public interface MemberBlockService { + Map getBlockedUsers(Long blockerOrgId, List userOrgIds); +} \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java new file mode 100644 index 00000000..9d600a0e --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java @@ -0,0 +1,27 @@ +package org.sopt.makers.crew.main.external.playground.service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.sopt.makers.crew.main.external.playground.entity.member_block.MemberBlock; +import org.sopt.makers.crew.main.external.playground.entity.member_block.MemberBlockRepository; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class MemberBlockServiceImpl implements MemberBlockService { + + private final MemberBlockRepository memberBlockRepository; + + @Override + public Map getBlockedUsers(Long blockerOrgId, List userOrgIds) { + List memberBlocks = memberBlockRepository.findAllByBlockerAndIsBlockedTrue(blockerOrgId); + + return memberBlocks.stream() + .filter(memberBlock -> userOrgIds.contains(memberBlock.getBlockedMember())) + .collect(Collectors.toMap(MemberBlock::getBlockedMember, memberBlock -> true)); + } +} \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java index e4465313..aa189606 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java @@ -1,15 +1,7 @@ package org.sopt.makers.crew.main.post.v2; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import java.security.Principal; + import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; @@ -27,78 +19,70 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + @Tag(name = "게시글") public interface PostV2Api { - @Operation(summary = "모임 게시글 작성") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content), - }) - ResponseEntity createPost( - @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal); + @Operation(summary = "모임 게시글 작성") + @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content),}) + ResponseEntity createPost(@Valid @RequestBody PostV2CreatePostBodyDto requestBody, + Principal principal); - @Operation(summary = "모임 게시글 목록 조회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), - }) - @Parameters({ - @Parameter(name = "page", description = "페이지, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), - @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), - @Parameter(name = "meetingId", description = "모임 id", example = "0", schema = @Schema(type = "integer", format = "int32"))}) - ResponseEntity getPosts( - @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, - Principal principal); + @Operation(summary = "모임 게시글 목록 조회") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content),}) + @Parameters({ + @Parameter(name = "page", description = "페이지, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "meetingId", description = "모임 id", example = "0", schema = @Schema(type = "integer", format = "int32"))}) + ResponseEntity getPosts( + @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, Principal principal); - @Operation(summary = "게시글에서 멘션하기") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - }) - ResponseEntity mentionUserInPost( - @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal); + @Operation(summary = "게시글에서 멘션하기") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"),}) + ResponseEntity mentionUserInPost(@Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, + Principal principal); - @Operation(summary = "모임 게시글 조회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content) - }) - ResponseEntity getPost(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 조회") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content)}) + ResponseEntity getPost(@PathVariable Integer postId, Principal principal); - @Operation(summary = "모임 게시글 개수 조회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content) - }) - ResponseEntity getPostCount(@RequestParam Integer meetingId); + @Operation(summary = "모임 게시글 개수 조회") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content)}) + ResponseEntity getPostCount(@RequestParam Integer meetingId); - @Operation(summary = "모임 게시글 삭제") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content) - }) - ResponseEntity deletePost(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 삭제") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content)}) + ResponseEntity deletePost(@PathVariable Integer postId, Principal principal); - @Operation(summary = "모임 게시글 수정") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미지는 최대 10개까지만 업로드 가능합니다.\"", content = @Content), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content) - }) - ResponseEntity updatePost(@PathVariable Integer postId, - @RequestBody PostV2UpdatePostBodyDto requestBody, - Principal principal); + @Operation(summary = "모임 게시글 수정") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미지는 최대 10개까지만 업로드 가능합니다.\"", content = @Content), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content)}) + ResponseEntity updatePost(@PathVariable Integer postId, + @RequestBody PostV2UpdatePostBodyDto requestBody, Principal principal); - @Operation(summary = "모임 게시글 신고") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "성공"), - @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미 신고한 게시글입니다.\"", content = @Content) - }) - ResponseEntity reportPost(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 신고") + @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "성공"), + @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미 신고한 게시글입니다.\"", content = @Content)}) + ResponseEntity reportPost(@PathVariable Integer postId, Principal principal); - @Operation(summary = "모임 게시글 좋아요 토글") - @ApiResponse(responseCode = "201", description = "성공") - ResponseEntity switchPostLike(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 좋아요 토글") + @ApiResponse(responseCode = "201", description = "성공") + ResponseEntity switchPostLike(@PathVariable Integer postId, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java index 81361c55..76b61f99 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java @@ -1,9 +1,7 @@ package org.sopt.makers.crew.main.post.v2; -import io.swagger.v3.oas.annotations.Parameter; -import jakarta.validation.Valid; import java.security.Principal; -import lombok.RequiredArgsConstructor; + import org.sopt.makers.crew.main.common.util.UserUtil; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; @@ -31,91 +29,93 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + @RestController @RequestMapping("/post/v2") @RequiredArgsConstructor public class PostV2Controller implements PostV2Api { - private final PostV2Service postV2Service; + private final PostV2Service postV2Service; - @Override - @PostMapping() - @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity createPost( - @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.createPost(requestBody, userId)); - } + @Override + @PostMapping() + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity createPost( + @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.createPost(requestBody, userId)); + } - @Override - @GetMapping() - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getPosts( - @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.getPosts(queryCommand, userId)); - } + @Override + @GetMapping() + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPosts( + @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.getPosts(queryCommand, userId)); + } - @Override - @PostMapping("/mention") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity mentionUserInPost( - @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - postV2Service.mentionUserInPost(requestBody, userId); - return ResponseEntity.status(HttpStatus.OK).build(); - } + @Override + @PostMapping("/mention") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity mentionUserInPost(@Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + postV2Service.mentionUserInPost(requestBody, userId); + return ResponseEntity.status(HttpStatus.OK).build(); + } - @Override - @GetMapping("/{postId}") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getPost(@PathVariable Integer postId, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.getPost(userId, postId)); - } + @Override + @GetMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.getPost(userId, postId)); + } - @Override - @GetMapping("/count") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getPostCount(@RequestParam Integer meetingId) { - return ResponseEntity.ok(postV2Service.getPostCount(meetingId)); - } + @Override + @GetMapping("/count") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPostCount(@RequestParam Integer meetingId) { + return ResponseEntity.ok(postV2Service.getPostCount(meetingId)); + } - @Override - @DeleteMapping("/{postId}") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity deletePost(@PathVariable Integer postId, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - postV2Service.deletePost(postId, userId); - return ResponseEntity.ok().build(); - } + @Override + @DeleteMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity deletePost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + postV2Service.deletePost(postId, userId); + return ResponseEntity.ok().build(); + } - @Override - @PutMapping("/{postId}") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity updatePost(@PathVariable Integer postId, - @RequestBody PostV2UpdatePostBodyDto requestBody, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.updatePost(postId, requestBody, userId)); - } + @Override + @PutMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity updatePost(@PathVariable Integer postId, + @RequestBody PostV2UpdatePostBodyDto requestBody, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.updatePost(postId, requestBody, userId)); + } - @Override - @PostMapping("/{postId}/report") - @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity reportPost(@PathVariable Integer postId, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.reportPost(postId, userId)); - } + @Override + @PostMapping("/{postId}/report") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity reportPost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.reportPost(postId, userId)); + } - @Override - @PostMapping("/{postId}/like") - @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity switchPostLike(@PathVariable Integer postId, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.switchPostLike(postId, userId)); - } + @Override + @PostMapping("/{postId}/like") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity switchPostLike(@PathVariable Integer postId, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.switchPostLike(postId, userId)); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java index 60e3e3b9..5fb3bef5 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java @@ -1,75 +1,75 @@ package org.sopt.makers.crew.main.post.v2.dto.response; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.querydsl.core.annotations.QueryProjection; import java.time.LocalDateTime; +import com.querydsl.core.annotations.QueryProjection; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter -@Schema(name = "PostDetailBaseDto", description = "게시글 객체 Dto") +@Schema(name = "PostDetailBaseDto", description = "게시글의 기본 정보를 담고 있는 Dto") public class PostDetailBaseDto { - @Schema(description = "게시글 id", example = "1") - @NotNull - private final Integer id; - - @Schema(description = "게시글 제목", example = "게시글 제목입니다.") - @NotNull - private final String title; - - @Schema(description = "게시글 내용", example = "게시글 내용입니다.") - @NotNull - private final String contents; - - @Schema(description = "게시글 생성일자", example = "2024-07-31T15:30:00") - @NotNull - private final LocalDateTime createdDate; - - @Schema(description = "게시글 이미지", example = "[\"url1\", \"url2\"]") - @NotNull - private final String[] images; - - @Schema(description = "게시글 작성자 객체", example = "") - @NotNull - private final PostWriterInfoDto user; - - @Schema(description = "게시글 좋아요 갯수", example = "20") - private final int likeCount; - - //* 본인이 좋아요를 눌렀는지 여부 - @Schema(description = "게시글 좋아요 여부", example = "true") - @NotNull - private final Boolean isLiked; - - @Schema(description = "게시글 조회수", example = "30") - @NotNull - private final int viewCount; - - @Schema(description = "게시글 댓글 수", example = "5") - @NotNull - private final int commentCount; - - @Schema(description = "게시글에 대한 모임", example = "") - @NotNull - private final PostMeetingDto meeting; - - @QueryProjection - public PostDetailBaseDto(Integer id, String title, String contents, LocalDateTime createdDate, String[] images, - PostWriterInfoDto user, int likeCount, boolean isLiked, int viewCount, int commentCount, - PostMeetingDto meeting) { - this.id = id; - this.title = title; - this.contents = contents; - this.createdDate = createdDate; - this.images = images; - this.user = user; - this.likeCount = likeCount; - this.isLiked = isLiked; - this.viewCount = viewCount; - this.commentCount = commentCount; - this.meeting = meeting; - } + @Schema(description = "게시글 id", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "게시글 제목", example = "게시글 제목입니다.") + @NotNull + private final String title; + + @Schema(description = "게시글 내용", example = "게시글 내용입니다.") + @NotNull + private final String contents; + + @Schema(description = "게시글 생성일자", example = "2024-07-31T15:30:00") + @NotNull + private final LocalDateTime createdDate; + + @Schema(description = "게시글 이미지", example = "[\"url1\", \"url2\"]") + @NotNull + private final String[] images; + + @Schema(description = "게시글 작성자 객체", example = "") + @NotNull + private final PostWriterInfoDto user; + + @Schema(description = "게시글 좋아요 갯수", example = "20") + private final int likeCount; + + //* 본인이 좋아요를 눌렀는지 여부 + @Schema(description = "게시글 좋아요 여부", example = "true") + @NotNull + private final Boolean isLiked; + + @Schema(description = "게시글 조회수", example = "30") + @NotNull + private final int viewCount; + + @Schema(description = "게시글 댓글 수", example = "5") + @NotNull + private final int commentCount; + + @Schema(description = "게시글에 대한 모임", example = "") + @NotNull + private final PostMeetingDto meeting; + + @QueryProjection + public PostDetailBaseDto(Integer id, String title, String contents, LocalDateTime createdDate, String[] images, + PostWriterInfoDto user, int likeCount, boolean isLiked, int viewCount, int commentCount, + PostMeetingDto meeting) { + this.id = id; + this.title = title; + this.contents = contents; + this.createdDate = createdDate; + this.images = images; + this.user = user; + this.likeCount = likeCount; + this.isLiked = isLiked; + this.viewCount = viewCount; + this.commentCount = commentCount; + this.meeting = meeting; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java index a05e19e4..739f6e5d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java @@ -10,73 +10,62 @@ @Getter @AllArgsConstructor(staticName = "of") -@Schema(name = "PostDetailResponseDto", description = "게시글 객체 Dto") +@Schema(name = "PostDetailResponseDto", description = "게시글의 기본 정보 + 댓글 썸네일 이미지 리스트 정보을 담고 있는 DTO") public class PostDetailResponseDto { - @Schema(description = "게시글 id", example = "1") - @NotNull - private final Integer id; - - @Schema(description = "게시글 제목", example = "게시글 제목입니다.") - @NotNull - private final String title; - - @Schema(description = "게시글 내용", example = "게시글 내용입니다.") - @NotNull - private final String contents; - - @Schema(description = "게시글 생성 일자", example = "2024-05-31T15:30:00") - @NotNull - private final LocalDateTime createdDate; - - @Schema(description = "게시글 이미지 목록", example = "[\"url1\", \"url2\"]") - @NotNull - private final String[] images; - - @Schema(description = "게시글 생성 유저 객체", example = "") - @NotNull - private final PostWriterInfoDto user; - - @Schema(description = "게시글 좋아요 수", example = "20") - @NotNull - private final int likeCount; - - @Schema(description = "게시글 좋아요 여부", example = "true") - @NotNull - private final Boolean isLiked; - - @Schema(description = "게시글 조회수", example = "200") - @NotNull - private final int viewCount; - - @Schema(description = "게시글 댓글 수", example = "30") - @NotNull - private final int commentCount; - - @Schema(description = "게시글에 해당하는 모임", example = "") - @NotNull - private final PostMeetingDto meeting; - - @Schema(description = "댓글 작성자 썸네일 목록", example = "[\"url1\", \"url2\"]") - @NotNull - private final List commenterThumbnails; - - public static PostDetailResponseDto of(PostDetailBaseDto postDetail, - CommenterThumbnails postTopCommenterThumbnails) { - List thumbnails = postTopCommenterThumbnails.getCommenterThumbnails(); - return PostDetailResponseDto.of( - postDetail.getId(), - postDetail.getTitle(), - postDetail.getContents(), - postDetail.getCreatedDate(), - postDetail.getImages(), - postDetail.getUser(), - postDetail.getLikeCount(), - postDetail.getIsLiked(), - postDetail.getViewCount(), - postDetail.getCommentCount(), - postDetail.getMeeting(), - thumbnails - ); - } + @Schema(description = "게시글 id", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "게시글 제목", example = "게시글 제목입니다.") + @NotNull + private final String title; + + @Schema(description = "게시글 내용", example = "게시글 내용입니다.") + @NotNull + private final String contents; + + @Schema(description = "게시글 생성 일자", example = "2024-05-31T15:30:00") + @NotNull + private final LocalDateTime createdDate; + + @Schema(description = "게시글 이미지 목록", example = "[\"url1\", \"url2\"]") + @NotNull + private final String[] images; + + @Schema(description = "게시글 생성 유저 객체", example = "") + @NotNull + private final PostWriterInfoDto user; + + @Schema(description = "게시글 좋아요 수", example = "20") + @NotNull + private final int likeCount; + + @Schema(description = "게시글 좋아요 여부", example = "true") + @NotNull + private final Boolean isLiked; + + @Schema(description = "게시글 조회수", example = "200") + @NotNull + private final int viewCount; + + @Schema(description = "게시글 댓글 수", example = "30") + @NotNull + private final int commentCount; + + @Schema(description = "게시글에 해당하는 모임", example = "") + @NotNull + private final PostMeetingDto meeting; + + @Schema(description = "댓글 작성자 썸네일 목록", example = "[\"url1\", \"url2\"]") + @NotNull + private final List commenterThumbnails; + + public static PostDetailResponseDto of(PostDetailBaseDto postDetail, + CommenterThumbnails postTopCommenterThumbnails) { + return PostDetailResponseDto.of(postDetail.getId(), postDetail.getTitle(), postDetail.getContents(), + postDetail.getCreatedDate(), postDetail.getImages(), postDetail.getUser(), postDetail.getLikeCount(), + postDetail.getIsLiked(), postDetail.getViewCount(), postDetail.getCommentCount(), postDetail.getMeeting(), + postTopCommenterThumbnails.getCommenterThumbnails()); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailWithBlockStatusResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailWithBlockStatusResponseDto.java new file mode 100644 index 00000000..15c3d408 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailWithBlockStatusResponseDto.java @@ -0,0 +1,20 @@ +package org.sopt.makers.crew.main.post.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(staticName = "of") +@Schema(name = "PostDetailWithBlockStatusResponseDto", description = "게시글 객체 + 차단된 유저의 게시물 여부 true/false Dto") +public class PostDetailWithBlockStatusResponseDto { + + @Schema(description = "게시글의 기본 정보 + 댓글 썸네일 이미지 리스트 정보을 담고 있는 DTO", example = "") + @NotNull + private final PostDetailResponseDto postDetail; + + @Schema(description = "차단된 유저의 게시물인지 여부", example = "false") + @NotNull + private final boolean isBlockedPost; +} \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java index dd38fa9b..fe7c0d5c 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java @@ -14,10 +14,11 @@ @Schema(name = "PostV2GetPostsResponseDto", description = "게시글 조회 응답 Dto") public class PostV2GetPostsResponseDto { - @Schema(description = "게시글 객체", example = "") + @Schema(description = "게시글의 기본 정보 + 댓글 썸네일 이미지 리스트 정보 + 차단된 유저의 게시물인지 아닌지 정보를 담고 있는 DTO") @NotNull - private final List posts; + private final List posts; + @Schema(description = "페이지 메타 정보") @NotNull private final PageMetaDto meta; diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java index 4e7da2c4..3b073371 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java @@ -14,21 +14,21 @@ public interface PostV2Service { - PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId); + PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId); - PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId); + PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId); - PostDetailBaseDto getPost(Integer userId, Integer postId); + PostDetailBaseDto getPost(Integer userId, Integer postId); - void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId); + void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId); - PostV2GetPostCountResponseDto getPostCount(Integer meetingId); + PostV2GetPostCountResponseDto getPostCount(Integer meetingId); - void deletePost(Integer postId, Integer userId); + void deletePost(Integer postId, Integer userId); - PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId); + PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId); - PostV2ReportResponseDto reportPost(Integer postId, Integer userId); + PostV2ReportResponseDto reportPost(Integer postId, Integer userId); - PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer userId); + PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java index 41fad4b2..f1d6acd9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java @@ -1,16 +1,12 @@ package org.sopt.makers.crew.main.post.v2.service; -import static java.util.stream.Collectors.toList; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.ALREADY_REPORTED_POST; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FORBIDDEN_EXCEPTION; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.MAX_IMAGE_UPLOAD_EXCEEDED; -import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE; -import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_PUSH_NOTIFICATION_TITLE; -import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.PUSH_NOTIFICATION_CATEGORY; +import static java.util.stream.Collectors.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; +import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.*; import java.util.List; - -import lombok.RequiredArgsConstructor; +import java.util.Map; +import java.util.stream.Collectors; import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.common.exception.ForbiddenException; @@ -33,6 +29,7 @@ import org.sopt.makers.crew.main.entity.report.ReportRepository; import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.entity.user.UserRepository; +import org.sopt.makers.crew.main.external.playground.service.MemberBlockService; import org.sopt.makers.crew.main.internal.notification.PushNotificationService; import org.sopt.makers.crew.main.internal.notification.dto.PushNotificationRequestDto; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; @@ -41,19 +38,22 @@ import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailWithBlockStatusResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; +import org.sopt.makers.crew.main.user.v2.service.UserV2Service; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import lombok.RequiredArgsConstructor; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -68,6 +68,8 @@ public class PostV2ServiceImpl implements PostV2Service { private final ReportRepository reportRepository; private final PushNotificationService pushNotificationService; + private final MemberBlockService memberBlockService; + private final UserV2Service userV2Service; @Value("${push-notification.web-url}") private String pushWebUrl; @@ -82,16 +84,14 @@ public class PostV2ServiceImpl implements PostV2Service { */ @Override @Transactional - public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, - Integer userId) { + public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId) { Meeting meeting = meetingRepository.findByIdOrThrow(requestBody.getMeetingId()); User user = userRepository.findByIdOrThrow(userId); List applies = applyRepository.findAllByMeetingId(meeting.getId()); boolean isInMeeting = applies.stream() - .anyMatch(apply -> apply.getUserId().equals(userId) - && apply.getStatus().equals(EnApplyStatus.APPROVE)); + .anyMatch(apply -> apply.getUserId().equals(userId) && apply.getStatus().equals(EnApplyStatus.APPROVE)); boolean isMeetingCreator = meeting.getUserId().equals(userId); @@ -109,27 +109,32 @@ public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBod Post savedPost = postRepository.save(post); - List userIdList = applyRepository.findAllByMeetingIdAndStatus(meeting.getId(), - EnApplyStatus.APPROVE) + List userIdList = applyRepository.findAllByMeetingIdAndStatus(meeting.getId(), EnApplyStatus.APPROVE) .stream() .map(apply -> String.valueOf(apply.getUser().getOrgId())) .collect(toList()); String[] userIds = userIdList.toArray(new String[0]); - String pushNotificationContent = String.format("[%s의 새 글] : \"%s\"", - user.getName(), post.getTitle()); + String pushNotificationContent = String.format("[%s의 새 글] : \"%s\"", user.getName(), post.getTitle()); String pushNotificationWeblink = pushWebUrl + "/detail?id=" + meeting.getId(); PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of(userIds, - NEW_POST_PUSH_NOTIFICATION_TITLE.getValue(), - pushNotificationContent, - PUSH_NOTIFICATION_CATEGORY.getValue(), pushNotificationWeblink); + NEW_POST_PUSH_NOTIFICATION_TITLE.getValue(), pushNotificationContent, PUSH_NOTIFICATION_CATEGORY.getValue(), + pushNotificationWeblink); pushNotificationService.sendPushNotification(pushRequestDto); return PostV2CreatePostResponseDto.of(savedPost.getId()); } + /** + * 모일 게시글 리스트 페이지네이션 조회 (12개) + * + * @param queryCommand 게시글 조회를 위한 쿼리 명령 객체 + * @param userId 게시글을 조회하는 사용자 id + * @return 게시글 정보(게시글 객체 + 댓글 단 사람의 썸네일 + 차단된 유저의 게시물 여부)와 페이지 메타 정보를 포함한 응답 DTO + * @apiNote 사용자가 차단한 유저의 게시물은 해당 게시물에 대한 차단 여부를 함께 반환 + */ @Override @Transactional(readOnly = true) public PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId) { @@ -139,10 +144,29 @@ public PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Inte PageOptionsDto pageOptionsDto = new PageOptionsDto(meetingPostListDtos.getPageable().getPageNumber() + 1, meetingPostListDtos.getPageable().getPageSize()); - PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, - (int)meetingPostListDtos.getTotalElements()); + PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, (int)meetingPostListDtos.getTotalElements()); - return PostV2GetPostsResponseDto.of(meetingPostListDtos.getContent(), pageMetaDto); + // 조회된 게시물의 작성자들의 플그 ID 리스트 가져오기 + List userOrgIds = meetingPostListDtos.getContent() + .stream() + .map(postDetail -> postDetail.getUser().getOrgId().longValue()) + .collect(Collectors.toList()); + + User user = userV2Service.getUserByUserId(userId); + Long orgId = user.getOrgId().longValue(); + + // 한 번의 호출로 현재 유저(차단자)가 위 플그 ID에 대한 차단 여부를 확인 + Map blockedPostMap = memberBlockService.getBlockedUsers(orgId, userOrgIds); + + List responseDtos = meetingPostListDtos.getContent() + .stream() + .map(postDetail -> { + boolean isBlockedPost = blockedPostMap.getOrDefault(postDetail.getUser().getOrgId().longValue(), false); + return PostDetailWithBlockStatusResponseDto.of(postDetail, isBlockedPost); + }) + .collect(Collectors.toList()); + + return PostV2GetPostsResponseDto.of(responseDtos, pageMetaDto); } /** @@ -162,21 +186,14 @@ public void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Int User user = userRepository.findByIdOrThrow(userId); Post post = postRepository.findByIdOrThrow(requestBody.getPostId()); - String pushNotificationContent = String.format("[%s의 글] : \"%s\"", - user.getName(), post.getTitle()); + String pushNotificationContent = String.format("[%s의 글] : \"%s\"", user.getName(), post.getTitle()); String pushNotificationWeblink = pushWebUrl + "/post?id=" + post.getId(); - String[] userOrgIds = requestBody.getOrgIds().stream() - .map(Object::toString) - .toArray(String[]::new); + String[] userOrgIds = requestBody.getOrgIds().stream().map(Object::toString).toArray(String[]::new); - PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of( - userOrgIds, - NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE.getValue(), - pushNotificationContent, - PUSH_NOTIFICATION_CATEGORY.getValue(), - pushNotificationWeblink - ); + PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of(userOrgIds, + NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE.getValue(), pushNotificationContent, + PUSH_NOTIFICATION_CATEGORY.getValue(), pushNotificationWeblink); pushNotificationService.sendPushNotification(pushRequestDto); } @@ -235,8 +252,7 @@ public PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBo post.updatePost(requestBody.getTitle(), requestBody.getContents(), requestBody.getImages()); return PostV2UpdatePostResponseDto.of(post.getId(), post.getTitle(), post.getContents(), - String.valueOf(time.now()), - post.getImages()); + String.valueOf(time.now()), post.getImages()); } /** @@ -258,11 +274,7 @@ public PostV2ReportResponseDto reportPost(Integer postId, Integer userId) { throw new BadRequestException(ALREADY_REPORTED_POST.getErrorCode()); } - Report report = Report.builder() - .post(post) - .postId(postId) - .userId(userId) - .build(); + Report report = Report.builder().post(post).postId(postId).userId(userId).build(); Report savedReport = reportRepository.save(report); @@ -288,10 +300,7 @@ public PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer us // 취소된 좋아요 정보가 없을 경우 if (deletedLikes == 0) { - Like newLike = Like.builder() - .postId(postId) - .userId(userId) - .build(); + Like newLike = Like.builder().postId(postId).userId(userId).build(); likeRepository.save(newLike); diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java index 1b71514d..deb1fb9b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java @@ -2,6 +2,7 @@ import java.util.List; +import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAppliedMeetingByUserResponseDto; @@ -20,5 +21,5 @@ public interface UserV2Service { UserV2GetCreatedMeetingByUserResponseDto getCreatedMeetingByUser(Integer userId); - + User getUserByUserId(Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java index 6bdc648d..17e792c5 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java @@ -5,8 +5,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import lombok.RequiredArgsConstructor; - import org.sopt.makers.crew.main.common.exception.BaseException; import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.apply.Applies; @@ -28,6 +26,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import lombok.RequiredArgsConstructor; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -45,19 +45,12 @@ public List getAllMeetingByUser(Integer use List myMeetings = meetingRepository.findAllByUserId(user.getId()); - List userJoinedList = Stream.concat( - myMeetings.stream(), + List userJoinedList = Stream.concat(myMeetings.stream(), applyRepository.findAllByUserIdAndStatus(userId, EnApplyStatus.APPROVE) .stream() - .map(apply -> apply.getMeeting()) - ) - .map(meeting -> UserV2GetAllMeetingByUserMeetingDto.of( - meeting.getId(), - meeting.getTitle(), - meeting.getDesc(), - meeting.getImageURL().get(0).getUrl(), - meeting.getCategory().getValue() - )) + .map(apply -> apply.getMeeting())) + .map(meeting -> UserV2GetAllMeetingByUserMeetingDto.of(meeting.getId(), meeting.getTitle(), + meeting.getDesc(), meeting.getImageURL().get(0).getUrl(), meeting.getCategory().getValue())) .sorted(Comparator.comparing(UserV2GetAllMeetingByUserMeetingDto::getId).reversed()) .collect(Collectors.toList()); @@ -110,12 +103,16 @@ public UserV2GetAppliedMeetingByUserResponseDto getAppliedMeetingByUser(Integer Applies allApplies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); List appliedMeetingByUserDtos = myApplies.stream() - .map(apply -> ApplyV2GetAppliedMeetingByUserResponseDto.of( - apply.getId(), apply.getStatus().getValue(), + .map(apply -> ApplyV2GetAppliedMeetingByUserResponseDto.of(apply.getId(), apply.getStatus().getValue(), MeetingV2GetCreatedMeetingByUserResponseDto.of(apply.getMeeting(), apply.getMeeting().getUser(), - allApplies.getApprovedCount(apply.getMeetingId()), time.now()) - )).toList(); + allApplies.getApprovedCount(apply.getMeetingId()), time.now()))) + .toList(); return UserV2GetAppliedMeetingByUserResponseDto.of(appliedMeetingByUserDtos); } + + @Override + public User getUserByUserId(Integer userId) { + return userRepository.findByIdOrThrow(userId); + } } diff --git a/main/src/main/resources/application-test.yml b/main/src/main/resources/application-test.yml index a0e60eb0..ad43358a 100644 --- a/main/src/main/resources/application-test.yml +++ b/main/src/main/resources/application-test.yml @@ -1,5 +1,3 @@ - - server: port: 4000 @@ -10,15 +8,16 @@ spring: import: application-secret.properties datasource: driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - url: jdbc:tc:postgresql:///test-database - # username: root - # password: + jdbc-url: jdbc:tc:postgresql:///crew + + playground-datasource: + jdbc-url: jdbc:tc:postgresql:///playground jpa: open-in-view: false hibernate: naming: physical-strategy: org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy - ddl-auto: create-drop + ddl-auto: none properties: hibernate: show_sql: true @@ -64,7 +63,7 @@ push-notification: push-server-url: ${DEV_PUSH_SERVER_URL} notice: - secret-key : ${NOTICE_SECRET_KEY} + secret-key: ${NOTICE_SECRET_KEY} playground: server: @@ -94,4 +93,9 @@ management: prometheus: metrics: export: - enabled: true \ No newline at end of file + enabled: true + +logging: + level: + org.hibernate.SQL: DEBUG # Hibernate의 SQL 쿼리 로그 레벨 설정 + org.hibernate.type: TRACE # SQL 파라미터 바인딩을 보기 위한 타입 정보 로그 출력 diff --git a/main/src/main/resources/schema.sql b/main/src/main/resources/schema.sql new file mode 100644 index 00000000..9b2d7880 --- /dev/null +++ b/main/src/main/resources/schema.sql @@ -0,0 +1,201 @@ +DROP TYPE IF EXISTS meeting_joinableparts_enum; + +create type meeting_joinableparts_enum as enum ('PM', 'DESIGN', 'IOS', 'ANDROID', 'SERVER', 'WEB'); + +create table if not exists "user" +( + id serial + constraint "PK_cace4a159ff9f2512dd42373760" + primary key + constraint "UQ_cace4a159ff9f2512dd42373760" + unique, + name varchar not null, + "orgId" integer not null, + "profileImage" varchar, + activities jsonb, + phone varchar +); + +-- alter table "user" +-- owner to songmingyu; + +create table if not exists meeting +( + id serial + constraint "PK_dccaf9e4c0e39067d82ccc7bb83" + primary key + constraint "UQ_dccaf9e4c0e39067d82ccc7bb83" + unique, + "userId" integer not null + constraint "FK_854982a74818bb6307419e0e6b8" + references "user" + on delete cascade, + title varchar not null, + category varchar not null, + "imageURL" jsonb not null, + "startDate" timestamp not null, + "endDate" timestamp not null, + capacity integer not null, + "desc" varchar not null, + "processDesc" varchar not null, + "mStartDate" timestamp not null, + "mEndDate" timestamp not null, + "leaderDesc" varchar not null, + "targetDesc" varchar not null, + note varchar, + "isMentorNeeded" boolean not null, + "canJoinOnlyActiveGeneration" boolean not null, + "targetActiveGeneration" integer, + "joinableParts" meeting_joinableparts_enum[], + "createdGeneration" integer default 32 +); + +create table if not exists apply +( + id serial + constraint "PK_c61ed680472aa0f58499175d902" + primary key, + type integer default 0 not null, + "meetingId" integer not null + constraint "FK_b130d23f4642d1ef51c6e54d257" + references meeting + on delete cascade, + "userId" integer not null + constraint "FK_359c8244808809db5ee96ed066e" + references "user" + on delete cascade, + content varchar not null, + "appliedDate" timestamp not null, + status integer default 0 not null +); + +-- alter table apply +-- owner to songmingyu; + +create index if not exists "meetingId_index" + on apply ("meetingId"); + +create index if not exists "userId_index" + on apply ("userId"); + +create table if not exists notice +( + id serial + constraint "PK_705062b14410ff1a04998f86d72" + primary key + constraint "UQ_705062b14410ff1a04998f86d72" + unique, + title varchar not null, + "subTitle" varchar not null, + contents varchar not null, + "createdDate" timestamp not null, + "exposeStartDate" timestamp not null, + "exposeEndDate" timestamp not null +); + +create table if not exists post +( + id serial + constraint "PK_be5fda3aac270b134ff9c21cdee" + primary key + constraint "UQ_be5fda3aac270b134ff9c21cdee" + unique, + contents varchar not null, + "createdDate" timestamp not null, + "updatedDate" timestamp not null, + "likeCount" integer default 0 not null, + "userId" integer not null + constraint "FK_5c1cf55c308037b5aca1038a131" + references "user", + "meetingId" integer not null + constraint "FK_85e980cf9166f5337c0b2b76bc0" + references meeting, + title varchar not null, + "viewCount" integer default 0 not null, + images text[], + "commentCount" integer default 0 not null +); + +-- alter table post +-- owner to songmingyu; + +create table if not exists comment +( + id serial + constraint "PK_0b0e4bbc8415ec426f87f3a88e2" + primary key + constraint "UQ_0b0e4bbc8415ec426f87f3a88e2" + unique, + contents varchar not null, + depth integer default 0 not null, + "order" integer default 0 not null, + "createdDate" timestamp not null, + "updatedDate" timestamp not null, + "likeCount" integer default 0 not null, + "userId" integer + constraint "FK_c0354a9a009d3bb45a08655ce3b" + references "user", + "postId" integer not null + constraint "FK_94a85bb16d24033a2afdd5df060" + references post + on delete cascade, + "parentId" integer +); + +create table if not exists "like" +( + id serial + constraint "PK_eff3e46d24d416b52a7e0ae4159" + primary key + constraint "UQ_eff3e46d24d416b52a7e0ae4159" + unique, + "createdDate" timestamp not null, + "userId" integer not null + constraint "FK_e8fb739f08d47955a39850fac23" + references "user", + "postId" integer + constraint "FK_3acf7c55c319c4000e8056c1279" + references post + on delete cascade, + "commentId" integer + constraint "FK_d86e0a3eeecc21faa0da415a18a" + references comment + on delete cascade +); + +create table if not exists report +( + id serial + constraint "PK_99e4d0bea58cba73c57f935a546" + primary key + constraint "UQ_99e4d0bea58cba73c57f935a546" + unique, + "createdDate" timestamp not null, + "commentId" integer + constraint "FK_97372830f2390803a3e2df4a46e" + references comment, + "userId" integer not null + constraint "FK_e347c56b008c2057c9887e230aa" + references "user" + on delete cascade, + "postId" integer + constraint "FK_4b6fe2df37305bc075a4a16d3ea" + references post + on delete cascade +); + +create table if not exists advertisement +( + id serial + primary key, + "advertisementLink" varchar(255), + "advertisementEndDate" timestamp(6), + "advertisementStartDate" timestamp(6), + priority bigint, + "advertisementCategory" varchar(255) + constraint "advertisement_advertisementCategory_check" + check (("advertisementCategory")::text = ANY +((ARRAY ['POST'::character varying, 'MEETING'::character varying])::text[])), + "advertisementDesktopImageUrl" varchar(255), + "advertisementMobileImageUrl" varchar(255) + ); \ No newline at end of file diff --git a/main/src/test/java/org/sopt/makers/crew/main/entity/user/UserRepositoryTest.java b/main/src/test/java/org/sopt/makers/crew/main/entity/user/UserRepositoryTest.java index 21d5a8b6..8251eb72 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/entity/user/UserRepositoryTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/entity/user/UserRepositoryTest.java @@ -2,17 +2,19 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import org.sopt.makers.crew.main.common.config.TestConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.context.annotation.Import; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlGroup; +import org.testcontainers.junit.jupiter.Testcontainers; -@DataJpaTest +@SpringBootTest +@Testcontainers @ActiveProfiles("test") -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Import(TestConfig.class) +@SqlGroup({ + @Sql(value = "/sql/delete-all-data.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) +}) public class UserRepositoryTest { @Autowired @@ -25,9 +27,10 @@ public class UserRepositoryTest { // when User savedUser = userRepository.save(user); + User foundUser = userRepository.findByIdOrThrow(savedUser.getId()); // then - Assertions.assertThat(savedUser) + Assertions.assertThat(foundUser) .extracting("name", "phone") .containsExactly(user.getName(), user.getPhone()); } diff --git a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/repository/ApplyRepositoryTest.java b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/repository/ApplyRepositoryTest.java index 3b961566..e261317e 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/repository/ApplyRepositoryTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/repository/ApplyRepositoryTest.java @@ -14,18 +14,18 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyInfoDto; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.context.annotation.Import; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.SqlGroup; +import org.testcontainers.junit.jupiter.Testcontainers; -@DataJpaTest +@SpringBootTest @ActiveProfiles("test") -@Import(TestConfig.class) +@Testcontainers @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @SqlGroup({ @Sql(value = "/sql/apply-repository-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD), diff --git a/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java b/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java index b677c9fa..2783bbe8 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java @@ -1,146 +1,143 @@ package org.sopt.makers.crew.main.post.v2.repository; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; + import org.junit.jupiter.api.Test; -import org.sopt.makers.crew.main.common.config.TestConfig; import org.sopt.makers.crew.main.entity.post.PostRepository; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailResponseDto; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.context.annotation.Import; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.SqlGroup; +import org.testcontainers.junit.jupiter.Testcontainers; -@DataJpaTest +@SpringBootTest @ActiveProfiles("test") -@Import(TestConfig.class) -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Testcontainers @SqlGroup({ - @Sql(value = "/sql/post-repository-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD), - @Sql(value = "/sql/delete-all-data.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD) + @Sql(value = "/sql/post-repository-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD), + @Sql(value = "/sql/delete-all-data.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD) }) public class PostRepositoryTest { - @Autowired - private PostRepository postRepository; - - @Test - void 모임_ID로_필터링해서_게시글_목록_조회() { - // given - int page = 1; - int take = 9; - Integer meetingId = 1; - PostGetPostsCommand queryCommand = new PostGetPostsCommand(meetingId, page, take); - Integer userId = 1; - - // when - Page postDetailJsonDtos = postRepository.findPostList(queryCommand, - PageRequest.of(page - 1, take), - userId); - - // then - assertThat(postDetailJsonDtos.getTotalElements()).isEqualTo(3); - assertThat(postDetailJsonDtos.getSize()).isEqualTo(take); - assertThat(postDetailJsonDtos.getNumber()).isEqualTo(page - 1); - assertThat(postDetailJsonDtos.getTotalPages()).isEqualTo(1); - - PostDetailResponseDto postDetailDto1 = postDetailJsonDtos.getContent().get(0); - assertThat(postDetailDto1) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(3, "제목3", "내용3", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 2, 0)), - 0, false, 0); - assertThat(postDetailDto1.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(1, "스터디 구합니다1", "행사"); - - PostDetailResponseDto postDetailDto2 = postDetailJsonDtos.getContent().get(1); - assertThat(postDetailDto2) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(2, "제목2", "내용2", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 1, 0)), - 0, false, 0); - assertThat(postDetailDto2.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(1, "스터디 구합니다1", "행사"); - - PostDetailResponseDto postDetailDto3 = postDetailJsonDtos.getContent().get(2); - assertThat(postDetailDto3) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(1, "제목1", "내용1", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 0, 0)), - 2, true, 5); - assertThat(postDetailDto3.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(1, "스터디 구합니다1", "행사"); - // 댓글 작성자 최대 3명 순서 검증 - assertThat(postDetailDto3.getCommenterThumbnails()) - .containsExactly("profile1.jpg", "profile2.jpg", "profile3.jpg"); - } - - @Test - void 게시글_목록_전체_조회() { - // given - int page = 1; - int take = 9; - PostGetPostsCommand queryCommand = new PostGetPostsCommand(null, page, take); - Integer userId = 1; - - // when - Page postDetailJsonDtos = postRepository.findPostList(queryCommand, - PageRequest.of(page - 1, take), - userId); - - // then - assertThat(postDetailJsonDtos.getTotalElements()).isEqualTo(5); - assertThat(postDetailJsonDtos.getSize()).isEqualTo(take); - assertThat(postDetailJsonDtos.getNumber()).isEqualTo(page - 1); - assertThat(postDetailJsonDtos.getTotalPages()).isEqualTo(1); - - PostDetailResponseDto postDetailDto1 = postDetailJsonDtos.getContent().get(0); - assertThat(postDetailDto1) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(5, "제목5", "내용5", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 4, 0)), - 0, false, 0); - assertThat(postDetailDto1.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(2, "스터디 구합니다2", "스터디"); - - PostDetailResponseDto postDetailDto2 = postDetailJsonDtos.getContent().get(4); - assertThat(postDetailDto2) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(1, "제목1", "내용1", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 0, 0)), - 2, true, 5); - assertThat(postDetailDto2.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(1, "스터디 구합니다1", "행사"); - } - - @Test - void 게시글_개수_조회() { - // given - int meetingId1 = 1; - int meetingId2 = 2; - - // when - Integer postCount1 = postRepository.countByMeetingId(meetingId1); - Integer postCount2 = postRepository.countByMeetingId(meetingId2); - - // then - assertThat(postCount1).isEqualTo(3); - assertThat(postCount2).isEqualTo(2); - } + @Autowired + private PostRepository postRepository; + + @Test + void 모임_ID로_필터링해서_게시글_목록_조회() { + // given + int page = 1; + int take = 9; + Integer meetingId = 1; + PostGetPostsCommand queryCommand = new PostGetPostsCommand(meetingId, page, take); + Integer userId = 1; + + // when + Page postDetailJsonDtos = postRepository.findPostList(queryCommand, + PageRequest.of(page - 1, take), userId); + + // then + assertThat(postDetailJsonDtos.getTotalElements()).isEqualTo(3); + assertThat(postDetailJsonDtos.getSize()).isEqualTo(take); + assertThat(postDetailJsonDtos.getNumber()).isEqualTo(page - 1); + assertThat(postDetailJsonDtos.getTotalPages()).isEqualTo(1); + + PostDetailResponseDto postDetailDto1 = postDetailJsonDtos.getContent().get(0); + assertThat(postDetailDto1) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(3, "제목3", "내용3", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 2, 0)), + 0, false, 0); + assertThat(postDetailDto1.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(1, "스터디 구합니다1", "행사"); + + PostDetailResponseDto postDetailDto2 = postDetailJsonDtos.getContent().get(1); + assertThat(postDetailDto2) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(2, "제목2", "내용2", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 1, 0)), + 0, false, 0); + assertThat(postDetailDto2.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(1, "스터디 구합니다1", "행사"); + + PostDetailResponseDto postDetailDto3 = postDetailJsonDtos.getContent().get(2); + assertThat(postDetailDto3) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(1, "제목1", "내용1", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 0, 0)), + 2, true, 5); + assertThat(postDetailDto3.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(1, "스터디 구합니다1", "행사"); + // 댓글 작성자 최대 3명 순서 검증 + assertThat(postDetailDto3.getCommenterThumbnails()) + .containsExactly("profile1.jpg", "profile2.jpg", "profile3.jpg"); + } + + @Test + void 게시글_목록_전체_조회() { + // given + int page = 1; + int take = 9; + PostGetPostsCommand queryCommand = new PostGetPostsCommand(null, page, take); + Integer userId = 1; + + // when + Page postDetailJsonDtos = postRepository.findPostList(queryCommand, + PageRequest.of(page - 1, take), + userId); + + // then + assertThat(postDetailJsonDtos.getTotalElements()).isEqualTo(5); + assertThat(postDetailJsonDtos.getSize()).isEqualTo(take); + assertThat(postDetailJsonDtos.getNumber()).isEqualTo(page - 1); + assertThat(postDetailJsonDtos.getTotalPages()).isEqualTo(1); + + PostDetailResponseDto postDetailDto1 = postDetailJsonDtos.getContent().get(0); + assertThat(postDetailDto1) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(5, "제목5", "내용5", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 4, 0)), + 0, false, 0); + assertThat(postDetailDto1.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(2, "스터디 구합니다2", "스터디"); + + PostDetailResponseDto postDetailDto2 = postDetailJsonDtos.getContent().get(4); + assertThat(postDetailDto2) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(1, "제목1", "내용1", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 0, 0)), + 2, true, 5); + assertThat(postDetailDto2.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(1, "스터디 구합니다1", "행사"); + } + + @Test + void 게시글_개수_조회() { + // given + int meetingId1 = 1; + int meetingId2 = 2; + + // when + Integer postCount1 = postRepository.countByMeetingId(meetingId1); + Integer postCount2 = postRepository.countByMeetingId(meetingId2); + + // then + assertThat(postCount1).isEqualTo(3); + assertThat(postCount2).isEqualTo(2); + } } diff --git a/main/src/test/resources/sql/apply-repository-test-data.sql b/main/src/test/resources/sql/apply-repository-test-data.sql index 3ddff246..00c2525e 100644 --- a/main/src/test/resources/sql/apply-repository-test-data.sql +++ b/main/src/test/resources/sql/apply-repository-test-data.sql @@ -1,5 +1,3 @@ -CREATE TYPE meeting_joinableparts_enum AS ENUM ('PM', 'DESIGN', 'IOS', 'ANDROID', 'SERVER', 'WEB'); - INSERT INTO "user" (id, name, "orgId", activities, "profileImage", phone) VALUES (1, '김삼순', 1001, '[{"part": "서버", "generation": 33}, {"part": "iOS", "generation": 32}]', @@ -14,30 +12,6 @@ VALUES (1, '김삼순', 1001, '[{"part": "iOS", "generation": 32}, {"part": "안드로이드", "generation": 29}]', 'profile4.jpg', '010-5555-5555'); -create table "meeting" ( - "canJoinOnlyActiveGeneration" boolean not null, - "capacity" integer not null, - "createdGeneration" integer not null, - "id" serial not null, - "isMentorNeeded" boolean not null, - "targetActiveGeneration" integer, - "userId" integer, - "endDate" TIMESTAMP not null, - "mEndDate" TIMESTAMP not null, - "mStartDate" TIMESTAMP not null, - "startDate" TIMESTAMP not null, - "category" varchar(255) not null, - "desc" varchar(255) not null, - "leaderDesc" varchar(255) not null, - "note" varchar(255), - "processDesc" varchar(255) not null, - "targetDesc" varchar(255) not null, - "title" varchar(255) not null, - "imageURL" jsonb, - "joinableParts" meeting_joinableparts_enum[], - primary key ("id") -); - INSERT INTO meeting (id, "userId", title, category, "imageURL", "startDate", "endDate", capacity, "desc", "processDesc", "mStartDate", "mEndDate", "leaderDesc", "targetDesc", note, "isMentorNeeded", "canJoinOnlyActiveGeneration", "createdGeneration", diff --git a/main/src/test/resources/sql/delete-all-data.sql b/main/src/test/resources/sql/delete-all-data.sql index 637ccb04..493bd583 100644 --- a/main/src/test/resources/sql/delete-all-data.sql +++ b/main/src/test/resources/sql/delete-all-data.sql @@ -2,9 +2,9 @@ DELETE FROM "apply"; DELETE FROM "comment"; DELETE FROM "like"; +DELETE FROM "post"; DELETE FROM "meeting"; DELETE FROM "notice"; -DELETE FROM "post"; DELETE FROM "user"; -- 시퀀스 초기화 diff --git a/main/src/test/resources/sql/post-repository-test-data.sql b/main/src/test/resources/sql/post-repository-test-data.sql index e2676f94..f381c0bb 100644 --- a/main/src/test/resources/sql/post-repository-test-data.sql +++ b/main/src/test/resources/sql/post-repository-test-data.sql @@ -1,5 +1,3 @@ -CREATE TYPE meeting_joinableparts_enum AS ENUM ('PM', 'DESIGN', 'IOS', 'ANDROID', 'SERVER', 'WEB'); - INSERT INTO "user" (id, name, "orgId", activities, "profileImage", phone) VALUES (1, '김삼순', 1001, '[{"part": "서버", "generation": 33}, {"part": "iOS", "generation": 32}]', @@ -14,30 +12,6 @@ VALUES (1, '김삼순', 1001, '[{"part": "iOS", "generation": 32}, {"part": "안드로이드", "generation": 29}]', 'profile4.jpg', '010-5555-5555'); -create table "meeting" ( - "canJoinOnlyActiveGeneration" boolean not null, - "capacity" integer not null, - "createdGeneration" integer not null, - "id" serial not null, - "isMentorNeeded" boolean not null, - "targetActiveGeneration" integer, - "userId" integer, - "endDate" TIMESTAMP not null, - "mEndDate" TIMESTAMP not null, - "mStartDate" TIMESTAMP not null, - "startDate" TIMESTAMP not null, - "category" varchar(255) not null, - "desc" varchar(255) not null, - "leaderDesc" varchar(255) not null, - "note" varchar(255), - "processDesc" varchar(255) not null, - "targetDesc" varchar(255) not null, - "title" varchar(255) not null, - "imageURL" jsonb, - "joinableParts" meeting_joinableparts_enum[], - primary key ("id") -); - INSERT INTO meeting (id, "userId", title, category, "imageURL", "startDate", "endDate", capacity, "desc", "processDesc", "mStartDate", "mEndDate", "leaderDesc", "targetDesc", note, "isMentorNeeded", "canJoinOnlyActiveGeneration", "createdGeneration", From f339c414bde2ede59b9e31ac10573a19bd17f450 Mon Sep 17 00:00:00 2001 From: mikekks Date: Sat, 21 Sep 2024 21:29:28 +0900 Subject: [PATCH 2/9] =?UTF-8?q?Revert=20"feat:=20=ED=94=BC=EB=93=9C(post)?= =?UTF-8?q?=20=EC=B0=A8=EB=8B=A8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=EC=B6=95?= =?UTF-8?q?=20(#387)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit aecb65d93e6e6d223e11c4e81842d74d4a57c454. --- main/build.gradle | 1 - .../common/config/CrewDatabaseConfig.java | 39 +-- .../main/common/config/JpaAuditingConfig.java | 31 +-- .../config/PlaygroundDataSourceConfig.java | 11 +- .../entity/post/PostSearchRepository.java | 4 +- .../entity/post/PostSearchRepositoryImpl.java | 109 ++++---- .../member_block/MemberBlockRepository.java | 5 +- .../service/MemberBlockService.java | 8 - .../service/MemberBlockServiceImpl.java | 27 -- .../makers/crew/main/post/v2/PostV2Api.java | 128 ++++++---- .../crew/main/post/v2/PostV2Controller.java | 150 +++++------ .../v2/dto/response/PostDetailBaseDto.java | 126 ++++----- .../dto/response/PostDetailResponseDto.java | 123 +++++---- .../PostDetailWithBlockStatusResponseDto.java | 20 -- .../response/PostV2GetPostsResponseDto.java | 5 +- .../main/post/v2/service/PostV2Service.java | 18 +- .../post/v2/service/PostV2ServiceImpl.java | 103 ++++---- .../main/user/v2/service/UserV2Service.java | 3 +- .../user/v2/service/UserV2ServiceImpl.java | 31 ++- main/src/main/resources/application-test.yml | 20 +- main/src/main/resources/schema.sql | 201 --------------- .../main/entity/user/UserRepositoryTest.java | 19 +- .../v2/repository/ApplyRepositoryTest.java | 8 +- .../v2/repository/PostRepositoryTest.java | 239 +++++++++--------- .../sql/apply-repository-test-data.sql | 26 ++ .../test/resources/sql/delete-all-data.sql | 2 +- .../sql/post-repository-test-data.sql | 26 ++ 27 files changed, 631 insertions(+), 852 deletions(-) delete mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java delete mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java delete mode 100644 main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailWithBlockStatusResponseDto.java delete mode 100644 main/src/main/resources/schema.sql diff --git a/main/build.gradle b/main/build.gradle index aa80ef21..ed190c18 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -36,7 +36,6 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation group: 'org.postgresql', name: 'postgresql', version: '42.6.0' implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.postgresql:postgresql:42.3.0' // jsonb 타입 핸들링 위함 implementation 'io.hypersistence:hypersistence-utils-hibernate-62:3.6.0' diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/CrewDatabaseConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/CrewDatabaseConfig.java index fbfb0886..27bfa98b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/CrewDatabaseConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/CrewDatabaseConfig.java @@ -1,6 +1,5 @@ package org.sopt.makers.crew.main.common.config; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -8,25 +7,19 @@ import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.CommandLineRunner; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; -import jakarta.persistence.EntityManagerFactory; - @Configuration @EnableTransactionManagement @EnableJpaRepositories( @@ -34,7 +27,7 @@ entityManagerFactoryRef = "primaryEntityManagerFactory", // EntityManager의 이름 transactionManagerRef = "primaryTransactionManager" // 트랜잭션 매니저의 이름 ) -@Profile({"local", "dev", "prod", "test"}) +@Profile({"local", "dev", "prod"}) public class CrewDatabaseConfig { @Bean @@ -59,13 +52,10 @@ public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory() { Map properties = new HashMap<>(); properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"); + properties.put("hibernate.hbm2ddl.auto", "validate"); properties.put("hibernate.format_sql", true); properties.put("hibernate.physical_naming_strategy", "org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy"); - - String[] activeProfiles = {"local", "dev", "prod"}; - if (Arrays.stream(activeProfiles).anyMatch(profile -> profile.equals(System.getProperty("spring.profiles.active")))) { - properties.put("hibernate.hbm2ddl.auto", "validate"); - } + properties.put("hibernate.globally_quoted_identifiers", true); em.setJpaPropertyMap(properties); return em; @@ -78,27 +68,4 @@ public PlatformTransactionManager primaryTransactionManager( ) { return new JpaTransactionManager(Objects.requireNonNull(localContainerEntityManagerFactoryBean.getObject())); } - - private final ResourceLoader resourceLoader; - - public CrewDatabaseConfig(ResourceLoader resourceLoader) { - this.resourceLoader = resourceLoader; - } - - @Bean - @Profile("test") - public CommandLineRunner init(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) { - return args -> executeSchemaSql(); - } - - private void executeSchemaSql() { - Resource resource = resourceLoader.getResource("classpath:schema.sql"); - ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(); - databasePopulator.addScript(resource); - try { - databasePopulator.populate(primaryDatasourceProperties().getConnection()); - } catch (Exception e) { - System.out.println(e); - } - } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/JpaAuditingConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/JpaAuditingConfig.java index b4b92297..ef4a1927 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/JpaAuditingConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/JpaAuditingConfig.java @@ -1,34 +1,21 @@ package org.sopt.makers.crew.main.common.config; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; - import com.querydsl.jpa.JPQLTemplates; import com.querydsl.jpa.impl.JPAQueryFactory; - import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @EnableJpaAuditing @Configuration public class JpaAuditingConfig { + @PersistenceContext + private EntityManager entityManager; - @PersistenceContext(unitName = "primaryEntityManagerFactory") - private EntityManager primaryEntityManager; - - @PersistenceContext(unitName = "secondEntityManagerFactory") - private EntityManager playgroundEntityManager; - - @Bean(name = "primaryQueryFactory") - @Primary - public JPAQueryFactory primaryQueryFactory() { - return new JPAQueryFactory(JPQLTemplates.DEFAULT, primaryEntityManager); - } - - @Bean(name = "playgroundQueryFactory") - public JPAQueryFactory playgroundQueryFactory() { - return new JPAQueryFactory(JPQLTemplates.DEFAULT, playgroundEntityManager); - } + @Bean + public JPAQueryFactory queryFactory() { + return new JPAQueryFactory(JPQLTemplates.DEFAULT, entityManager); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/PlaygroundDataSourceConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/PlaygroundDataSourceConfig.java index 5c4c255f..5cde2b6c 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/PlaygroundDataSourceConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/PlaygroundDataSourceConfig.java @@ -1,6 +1,5 @@ package org.sopt.makers.crew.main.common.config; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -27,7 +26,7 @@ entityManagerFactoryRef = "secondEntityManagerFactory", transactionManagerRef = "secondTransactionManager" ) -@Profile({"local", "dev", "prod", "test"}) +@Profile({"local", "dev", "prod"}) public class PlaygroundDataSourceConfig { @Bean @ConfigurationProperties("spring.playground-datasource") @@ -49,14 +48,10 @@ public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory() { Map properties = new HashMap<>(); properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"); + properties.put("hibernate.hbm2ddl.auto", "validate"); properties.put("hibernate.format_sql", true); properties.put("hibernate.physical_naming_strategy", "org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy"); - - String[] activeProfiles = {"local", "dev", "prod"}; - if (Arrays.stream(activeProfiles).anyMatch(profile -> profile.equals(System.getProperty("spring.profiles.active")))) { - properties.put("hibernate.hbm2ddl.auto", "validate"); - } - + properties.put("hibernate.globally_quoted_identifiers", true); em.setJpaPropertyMap(properties); return em; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java index cfb5d811..df3f5db2 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java @@ -7,7 +7,7 @@ import org.springframework.data.domain.Pageable; public interface PostSearchRepository { - Page findPostList(PostGetPostsCommand queryCommand, Pageable pageable, Integer userId); + Page findPostList(PostGetPostsCommand queryCommand, Pageable pageable, Integer userId); - PostDetailBaseDto findPost(Integer userId, Integer postId); + PostDetailBaseDto findPost(Integer userId, Integer postId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java index 3154aaaf..e6423581 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java @@ -1,11 +1,18 @@ package org.sopt.makers.crew.main.entity.post; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; -import static org.sopt.makers.crew.main.entity.comment.QComment.*; -import static org.sopt.makers.crew.main.entity.like.QLike.*; -import static org.sopt.makers.crew.main.entity.meeting.QMeeting.*; -import static org.sopt.makers.crew.main.entity.post.QPost.*; -import static org.sopt.makers.crew.main.entity.user.QUser.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_POST; +import static org.sopt.makers.crew.main.entity.comment.QComment.comment; +import static org.sopt.makers.crew.main.entity.like.QLike.like; +import static org.sopt.makers.crew.main.entity.meeting.QMeeting.meeting; +import static org.sopt.makers.crew.main.entity.post.QPost.post; +import static org.sopt.makers.crew.main.entity.user.QUser.user; + +import com.querydsl.core.group.GroupBy; +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.ArrayList; import java.util.Collections; @@ -13,6 +20,8 @@ import java.util.Map; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; + import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.response.CommenterThumbnails; @@ -27,15 +36,6 @@ import org.springframework.data.support.PageableExecutionUtils; import org.springframework.stereotype.Repository; -import com.querydsl.core.group.GroupBy; -import com.querydsl.core.types.ExpressionUtils; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.jpa.JPAExpressions; -import com.querydsl.jpa.impl.JPAQuery; -import com.querydsl.jpa.impl.JPAQueryFactory; - -import lombok.RequiredArgsConstructor; - @Repository @RequiredArgsConstructor public class PostSearchRepositoryImpl implements PostSearchRepository { @@ -45,24 +45,31 @@ public class PostSearchRepositoryImpl implements PostSearchRepository { public Page findPostList(PostGetPostsCommand queryCommand, Pageable pageable, Integer userId) { Integer meetingId = queryCommand.getMeetingId().orElse(null); - List content = getContentList(pageable, meetingId, userId); + List content = getContentList(pageable, meetingId, userId); JPAQuery countQuery = getCount(meetingId); - return PageableExecutionUtils.getPage(content, PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()), - countQuery::fetchFirst); + return PageableExecutionUtils.getPage(content, + PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()), countQuery::fetchFirst); } @Override public PostDetailBaseDto findPost(Integer userId, Integer postId) { - PostDetailBaseDto postDetail = queryFactory.select( - new QPostDetailBaseDto(post.id, post.title, post.contents, post.createdDate, post.images, - new QPostWriterInfoDto(post.user.id, post.user.orgId, post.user.name, post.user.profileImage), - post.likeCount, ExpressionUtils.as( - JPAExpressions.selectFrom(like).where(like.postId.eq(post.id).and(like.userId.eq(userId))).exists(), - "isLiked"), post.viewCount, post.commentCount, - new QPostMeetingDto(post.meeting.id, post.meeting.title, post.meeting.category, post.meeting.imageURL, - post.meeting.desc))) + PostDetailBaseDto postDetail = queryFactory + .select(new QPostDetailBaseDto( + post.id, post.title, post.contents, post.createdDate, post.images, + new QPostWriterInfoDto(post.user.id, post.user.orgId, post.user.name, post.user.profileImage), + post.likeCount, + ExpressionUtils.as( + JPAExpressions.selectFrom(like) + .where(like.postId.eq(post.id).and(like.userId.eq(userId))) + .exists() + , "isLiked" + ), + post.viewCount, post.commentCount, + new QPostMeetingDto(post.meeting.id, post.meeting.title, post.meeting.category, post.meeting.imageURL, + post.meeting.desc) + )) .from(post) .innerJoin(post.meeting, meeting) .innerJoin(post.user, user) @@ -76,15 +83,25 @@ public PostDetailBaseDto findPost(Integer userId, Integer postId) { return postDetail; } - private List getContentList(Pageable pageable, Integer meetingId, Integer userId) { - List postDetails = queryFactory.select( - new QPostDetailBaseDto(post.id, post.title, post.contents, post.createdDate, post.images, - new QPostWriterInfoDto(post.user.id, post.user.orgId, post.user.name, post.user.profileImage), - post.likeCount, ExpressionUtils.as( - JPAExpressions.selectFrom(like).where(like.postId.eq(post.id).and(like.userId.eq(userId))).exists(), - "isLiked"), post.viewCount, post.commentCount, - new QPostMeetingDto(post.meeting.id, post.meeting.title, post.meeting.category, post.meeting.imageURL, - post.meeting.desc))) + private List getContentList(Pageable pageable, Integer meetingId, + Integer userId) { + List responseDtos = new ArrayList<>(); + + List postDetailList = queryFactory + .select(new QPostDetailBaseDto( + post.id, post.title, post.contents, post.createdDate, post.images, + new QPostWriterInfoDto(post.user.id, post.user.orgId, post.user.name, post.user.profileImage), + post.likeCount, + ExpressionUtils.as( + JPAExpressions.selectFrom(like) + .where(like.postId.eq(post.id).and(like.userId.eq(userId))) + .exists() + , "isLiked" + ), + post.viewCount, post.commentCount, + new QPostMeetingDto(post.meeting.id, post.meeting.title, post.meeting.category, post.meeting.imageURL, + post.meeting.desc) + )) .from(post) .innerJoin(post.user, user) .innerJoin(post.meeting, meeting) @@ -95,11 +112,13 @@ private List getContentList(Pageable pageable, Integer me .fetch(); // 모든 게시글 ID를 추출 - List postIds = postDetails.stream().map(PostDetailBaseDto::getId).collect(Collectors.toList()); + List postIds = postDetailList.stream() + .map(PostDetailBaseDto::getId) + .collect(Collectors.toList()); - // 게시글 ID 리스트를 사용하여 모든 댓글 작성자의 프로필 이미지를 조회 - Map> commenterThumbnailsMap = queryFactory.select(comment.post.id, - comment.user.profileImage) + // 게시글 ID 리스트를 사용하여 한 번에 모든 댓글 작성자의 프로필 이미지를 조회 + Map> commenterThumbnailsMap = queryFactory + .select(comment.post.id, comment.user.profileImage) .from(comment) .where(comment.post.id.in(postIds)) .groupBy(comment.post.id, comment.user.id, comment.user.profileImage) @@ -108,10 +127,10 @@ private List getContentList(Pageable pageable, Integer me .transform(GroupBy.groupBy(comment.post.id).as(GroupBy.list(comment.user.profileImage))); // 각 게시글별로 댓글 작성자의 프로필 이미지 리스트를 설정 - List responseDtos = new ArrayList<>(); - for (PostDetailBaseDto postDetail : postDetails) { + for (PostDetailBaseDto postDetail : postDetailList) { CommenterThumbnails commenterThumbnails = new CommenterThumbnails( - commenterThumbnailsMap.getOrDefault(postDetail.getId(), Collections.emptyList())); + commenterThumbnailsMap.getOrDefault(postDetail.getId(), + Collections.emptyList())); responseDtos.add(PostDetailResponseDto.of(postDetail, commenterThumbnails)); } @@ -119,10 +138,14 @@ private List getContentList(Pageable pageable, Integer me } private JPAQuery getCount(Integer meetingId) { - return queryFactory.select(post.count()).from(post).where(meetingIdEq(meetingId)); + return queryFactory + .select(post.count()) + .from(post) + .where(meetingIdEq(meetingId)); } private BooleanExpression meetingIdEq(Integer meetingId) { return meetingId == null ? null : post.meetingId.eq(meetingId); } + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/entity/member_block/MemberBlockRepository.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/entity/member_block/MemberBlockRepository.java index 628aa4c5..f7e9089f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/external/playground/entity/member_block/MemberBlockRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/entity/member_block/MemberBlockRepository.java @@ -1,9 +1,6 @@ package org.sopt.makers.crew.main.external.playground.entity.member_block; -import java.util.List; - import org.springframework.data.jpa.repository.JpaRepository; public interface MemberBlockRepository extends JpaRepository { - List findAllByBlockerAndIsBlockedTrue(Long orgId); -} \ No newline at end of file +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java deleted file mode 100644 index d125cf78..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.sopt.makers.crew.main.external.playground.service; - -import java.util.List; -import java.util.Map; - -public interface MemberBlockService { - Map getBlockedUsers(Long blockerOrgId, List userOrgIds); -} \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java deleted file mode 100644 index 9d600a0e..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.sopt.makers.crew.main.external.playground.service; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.sopt.makers.crew.main.external.playground.entity.member_block.MemberBlock; -import org.sopt.makers.crew.main.external.playground.entity.member_block.MemberBlockRepository; -import org.springframework.stereotype.Service; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class MemberBlockServiceImpl implements MemberBlockService { - - private final MemberBlockRepository memberBlockRepository; - - @Override - public Map getBlockedUsers(Long blockerOrgId, List userOrgIds) { - List memberBlocks = memberBlockRepository.findAllByBlockerAndIsBlockedTrue(blockerOrgId); - - return memberBlocks.stream() - .filter(memberBlock -> userOrgIds.contains(memberBlock.getBlockedMember())) - .collect(Collectors.toMap(MemberBlock::getBlockedMember, memberBlock -> true)); - } -} \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java index aa189606..e4465313 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java @@ -1,7 +1,15 @@ package org.sopt.makers.crew.main.post.v2; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import java.security.Principal; - import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; @@ -19,70 +27,78 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; - @Tag(name = "게시글") public interface PostV2Api { - @Operation(summary = "모임 게시글 작성") - @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content),}) - ResponseEntity createPost(@Valid @RequestBody PostV2CreatePostBodyDto requestBody, - Principal principal); + @Operation(summary = "모임 게시글 작성") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content), + }) + ResponseEntity createPost( + @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal); - @Operation(summary = "모임 게시글 목록 조회") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content),}) - @Parameters({ - @Parameter(name = "page", description = "페이지, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), - @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), - @Parameter(name = "meetingId", description = "모임 id", example = "0", schema = @Schema(type = "integer", format = "int32"))}) - ResponseEntity getPosts( - @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, Principal principal); + @Operation(summary = "모임 게시글 목록 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), + }) + @Parameters({ + @Parameter(name = "page", description = "페이지, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "meetingId", description = "모임 id", example = "0", schema = @Schema(type = "integer", format = "int32"))}) + ResponseEntity getPosts( + @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, + Principal principal); - @Operation(summary = "게시글에서 멘션하기") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"),}) - ResponseEntity mentionUserInPost(@Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, - Principal principal); + @Operation(summary = "게시글에서 멘션하기") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + }) + ResponseEntity mentionUserInPost( + @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal); - @Operation(summary = "모임 게시글 조회") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content)}) - ResponseEntity getPost(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content) + }) + ResponseEntity getPost(@PathVariable Integer postId, Principal principal); - @Operation(summary = "모임 게시글 개수 조회") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content)}) - ResponseEntity getPostCount(@RequestParam Integer meetingId); + @Operation(summary = "모임 게시글 개수 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content) + }) + ResponseEntity getPostCount(@RequestParam Integer meetingId); - @Operation(summary = "모임 게시글 삭제") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content)}) - ResponseEntity deletePost(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 삭제") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content) + }) + ResponseEntity deletePost(@PathVariable Integer postId, Principal principal); - @Operation(summary = "모임 게시글 수정") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미지는 최대 10개까지만 업로드 가능합니다.\"", content = @Content), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content)}) - ResponseEntity updatePost(@PathVariable Integer postId, - @RequestBody PostV2UpdatePostBodyDto requestBody, Principal principal); + @Operation(summary = "모임 게시글 수정") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미지는 최대 10개까지만 업로드 가능합니다.\"", content = @Content), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content) + }) + ResponseEntity updatePost(@PathVariable Integer postId, + @RequestBody PostV2UpdatePostBodyDto requestBody, + Principal principal); - @Operation(summary = "모임 게시글 신고") - @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "성공"), - @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미 신고한 게시글입니다.\"", content = @Content)}) - ResponseEntity reportPost(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 신고") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "성공"), + @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미 신고한 게시글입니다.\"", content = @Content) + }) + ResponseEntity reportPost(@PathVariable Integer postId, Principal principal); - @Operation(summary = "모임 게시글 좋아요 토글") - @ApiResponse(responseCode = "201", description = "성공") - ResponseEntity switchPostLike(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 좋아요 토글") + @ApiResponse(responseCode = "201", description = "성공") + ResponseEntity switchPostLike(@PathVariable Integer postId, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java index 76b61f99..81361c55 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java @@ -1,7 +1,9 @@ package org.sopt.makers.crew.main.post.v2; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.validation.Valid; import java.security.Principal; - +import lombok.RequiredArgsConstructor; import org.sopt.makers.crew.main.common.util.UserUtil; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; @@ -29,93 +31,91 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import io.swagger.v3.oas.annotations.Parameter; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; - @RestController @RequestMapping("/post/v2") @RequiredArgsConstructor public class PostV2Controller implements PostV2Api { - private final PostV2Service postV2Service; + private final PostV2Service postV2Service; - @Override - @PostMapping() - @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity createPost( - @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.createPost(requestBody, userId)); - } + @Override + @PostMapping() + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity createPost( + @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.createPost(requestBody, userId)); + } - @Override - @GetMapping() - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getPosts( - @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.getPosts(queryCommand, userId)); - } + @Override + @GetMapping() + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPosts( + @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.getPosts(queryCommand, userId)); + } - @Override - @PostMapping("/mention") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity mentionUserInPost(@Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - postV2Service.mentionUserInPost(requestBody, userId); - return ResponseEntity.status(HttpStatus.OK).build(); - } + @Override + @PostMapping("/mention") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity mentionUserInPost( + @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + postV2Service.mentionUserInPost(requestBody, userId); + return ResponseEntity.status(HttpStatus.OK).build(); + } - @Override - @GetMapping("/{postId}") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getPost(@PathVariable Integer postId, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.getPost(userId, postId)); - } + @Override + @GetMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.getPost(userId, postId)); + } - @Override - @GetMapping("/count") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getPostCount(@RequestParam Integer meetingId) { - return ResponseEntity.ok(postV2Service.getPostCount(meetingId)); - } + @Override + @GetMapping("/count") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPostCount(@RequestParam Integer meetingId) { + return ResponseEntity.ok(postV2Service.getPostCount(meetingId)); + } - @Override - @DeleteMapping("/{postId}") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity deletePost(@PathVariable Integer postId, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - postV2Service.deletePost(postId, userId); - return ResponseEntity.ok().build(); - } + @Override + @DeleteMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity deletePost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + postV2Service.deletePost(postId, userId); + return ResponseEntity.ok().build(); + } - @Override - @PutMapping("/{postId}") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity updatePost(@PathVariable Integer postId, - @RequestBody PostV2UpdatePostBodyDto requestBody, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.updatePost(postId, requestBody, userId)); - } + @Override + @PutMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity updatePost(@PathVariable Integer postId, + @RequestBody PostV2UpdatePostBodyDto requestBody, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.updatePost(postId, requestBody, userId)); + } - @Override - @PostMapping("/{postId}/report") - @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity reportPost(@PathVariable Integer postId, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.reportPost(postId, userId)); - } + @Override + @PostMapping("/{postId}/report") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity reportPost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.reportPost(postId, userId)); + } - @Override - @PostMapping("/{postId}/like") - @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity switchPostLike(@PathVariable Integer postId, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.switchPostLike(postId, userId)); - } + @Override + @PostMapping("/{postId}/like") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity switchPostLike(@PathVariable Integer postId, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.switchPostLike(postId, userId)); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java index 5fb3bef5..60e3e3b9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java @@ -1,75 +1,75 @@ package org.sopt.makers.crew.main.post.v2.dto.response; -import java.time.LocalDateTime; - +import com.fasterxml.jackson.annotation.JsonProperty; import com.querydsl.core.annotations.QueryProjection; +import java.time.LocalDateTime; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter -@Schema(name = "PostDetailBaseDto", description = "게시글의 기본 정보를 담고 있는 Dto") +@Schema(name = "PostDetailBaseDto", description = "게시글 객체 Dto") public class PostDetailBaseDto { - @Schema(description = "게시글 id", example = "1") - @NotNull - private final Integer id; - - @Schema(description = "게시글 제목", example = "게시글 제목입니다.") - @NotNull - private final String title; - - @Schema(description = "게시글 내용", example = "게시글 내용입니다.") - @NotNull - private final String contents; - - @Schema(description = "게시글 생성일자", example = "2024-07-31T15:30:00") - @NotNull - private final LocalDateTime createdDate; - - @Schema(description = "게시글 이미지", example = "[\"url1\", \"url2\"]") - @NotNull - private final String[] images; - - @Schema(description = "게시글 작성자 객체", example = "") - @NotNull - private final PostWriterInfoDto user; - - @Schema(description = "게시글 좋아요 갯수", example = "20") - private final int likeCount; - - //* 본인이 좋아요를 눌렀는지 여부 - @Schema(description = "게시글 좋아요 여부", example = "true") - @NotNull - private final Boolean isLiked; - - @Schema(description = "게시글 조회수", example = "30") - @NotNull - private final int viewCount; - - @Schema(description = "게시글 댓글 수", example = "5") - @NotNull - private final int commentCount; - - @Schema(description = "게시글에 대한 모임", example = "") - @NotNull - private final PostMeetingDto meeting; - - @QueryProjection - public PostDetailBaseDto(Integer id, String title, String contents, LocalDateTime createdDate, String[] images, - PostWriterInfoDto user, int likeCount, boolean isLiked, int viewCount, int commentCount, - PostMeetingDto meeting) { - this.id = id; - this.title = title; - this.contents = contents; - this.createdDate = createdDate; - this.images = images; - this.user = user; - this.likeCount = likeCount; - this.isLiked = isLiked; - this.viewCount = viewCount; - this.commentCount = commentCount; - this.meeting = meeting; - } + @Schema(description = "게시글 id", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "게시글 제목", example = "게시글 제목입니다.") + @NotNull + private final String title; + + @Schema(description = "게시글 내용", example = "게시글 내용입니다.") + @NotNull + private final String contents; + + @Schema(description = "게시글 생성일자", example = "2024-07-31T15:30:00") + @NotNull + private final LocalDateTime createdDate; + + @Schema(description = "게시글 이미지", example = "[\"url1\", \"url2\"]") + @NotNull + private final String[] images; + + @Schema(description = "게시글 작성자 객체", example = "") + @NotNull + private final PostWriterInfoDto user; + + @Schema(description = "게시글 좋아요 갯수", example = "20") + private final int likeCount; + + //* 본인이 좋아요를 눌렀는지 여부 + @Schema(description = "게시글 좋아요 여부", example = "true") + @NotNull + private final Boolean isLiked; + + @Schema(description = "게시글 조회수", example = "30") + @NotNull + private final int viewCount; + + @Schema(description = "게시글 댓글 수", example = "5") + @NotNull + private final int commentCount; + + @Schema(description = "게시글에 대한 모임", example = "") + @NotNull + private final PostMeetingDto meeting; + + @QueryProjection + public PostDetailBaseDto(Integer id, String title, String contents, LocalDateTime createdDate, String[] images, + PostWriterInfoDto user, int likeCount, boolean isLiked, int viewCount, int commentCount, + PostMeetingDto meeting) { + this.id = id; + this.title = title; + this.contents = contents; + this.createdDate = createdDate; + this.images = images; + this.user = user; + this.likeCount = likeCount; + this.isLiked = isLiked; + this.viewCount = viewCount; + this.commentCount = commentCount; + this.meeting = meeting; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java index 739f6e5d..a05e19e4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java @@ -10,62 +10,73 @@ @Getter @AllArgsConstructor(staticName = "of") -@Schema(name = "PostDetailResponseDto", description = "게시글의 기본 정보 + 댓글 썸네일 이미지 리스트 정보을 담고 있는 DTO") +@Schema(name = "PostDetailResponseDto", description = "게시글 객체 Dto") public class PostDetailResponseDto { - @Schema(description = "게시글 id", example = "1") - @NotNull - private final Integer id; - - @Schema(description = "게시글 제목", example = "게시글 제목입니다.") - @NotNull - private final String title; - - @Schema(description = "게시글 내용", example = "게시글 내용입니다.") - @NotNull - private final String contents; - - @Schema(description = "게시글 생성 일자", example = "2024-05-31T15:30:00") - @NotNull - private final LocalDateTime createdDate; - - @Schema(description = "게시글 이미지 목록", example = "[\"url1\", \"url2\"]") - @NotNull - private final String[] images; - - @Schema(description = "게시글 생성 유저 객체", example = "") - @NotNull - private final PostWriterInfoDto user; - - @Schema(description = "게시글 좋아요 수", example = "20") - @NotNull - private final int likeCount; - - @Schema(description = "게시글 좋아요 여부", example = "true") - @NotNull - private final Boolean isLiked; - - @Schema(description = "게시글 조회수", example = "200") - @NotNull - private final int viewCount; - - @Schema(description = "게시글 댓글 수", example = "30") - @NotNull - private final int commentCount; - - @Schema(description = "게시글에 해당하는 모임", example = "") - @NotNull - private final PostMeetingDto meeting; - - @Schema(description = "댓글 작성자 썸네일 목록", example = "[\"url1\", \"url2\"]") - @NotNull - private final List commenterThumbnails; - - public static PostDetailResponseDto of(PostDetailBaseDto postDetail, - CommenterThumbnails postTopCommenterThumbnails) { - return PostDetailResponseDto.of(postDetail.getId(), postDetail.getTitle(), postDetail.getContents(), - postDetail.getCreatedDate(), postDetail.getImages(), postDetail.getUser(), postDetail.getLikeCount(), - postDetail.getIsLiked(), postDetail.getViewCount(), postDetail.getCommentCount(), postDetail.getMeeting(), - postTopCommenterThumbnails.getCommenterThumbnails()); - } + @Schema(description = "게시글 id", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "게시글 제목", example = "게시글 제목입니다.") + @NotNull + private final String title; + + @Schema(description = "게시글 내용", example = "게시글 내용입니다.") + @NotNull + private final String contents; + + @Schema(description = "게시글 생성 일자", example = "2024-05-31T15:30:00") + @NotNull + private final LocalDateTime createdDate; + + @Schema(description = "게시글 이미지 목록", example = "[\"url1\", \"url2\"]") + @NotNull + private final String[] images; + + @Schema(description = "게시글 생성 유저 객체", example = "") + @NotNull + private final PostWriterInfoDto user; + + @Schema(description = "게시글 좋아요 수", example = "20") + @NotNull + private final int likeCount; + + @Schema(description = "게시글 좋아요 여부", example = "true") + @NotNull + private final Boolean isLiked; + + @Schema(description = "게시글 조회수", example = "200") + @NotNull + private final int viewCount; + + @Schema(description = "게시글 댓글 수", example = "30") + @NotNull + private final int commentCount; + + @Schema(description = "게시글에 해당하는 모임", example = "") + @NotNull + private final PostMeetingDto meeting; + + @Schema(description = "댓글 작성자 썸네일 목록", example = "[\"url1\", \"url2\"]") + @NotNull + private final List commenterThumbnails; + + public static PostDetailResponseDto of(PostDetailBaseDto postDetail, + CommenterThumbnails postTopCommenterThumbnails) { + List thumbnails = postTopCommenterThumbnails.getCommenterThumbnails(); + return PostDetailResponseDto.of( + postDetail.getId(), + postDetail.getTitle(), + postDetail.getContents(), + postDetail.getCreatedDate(), + postDetail.getImages(), + postDetail.getUser(), + postDetail.getLikeCount(), + postDetail.getIsLiked(), + postDetail.getViewCount(), + postDetail.getCommentCount(), + postDetail.getMeeting(), + thumbnails + ); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailWithBlockStatusResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailWithBlockStatusResponseDto.java deleted file mode 100644 index 15c3d408..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailWithBlockStatusResponseDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.sopt.makers.crew.main.post.v2.dto.response; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor(staticName = "of") -@Schema(name = "PostDetailWithBlockStatusResponseDto", description = "게시글 객체 + 차단된 유저의 게시물 여부 true/false Dto") -public class PostDetailWithBlockStatusResponseDto { - - @Schema(description = "게시글의 기본 정보 + 댓글 썸네일 이미지 리스트 정보을 담고 있는 DTO", example = "") - @NotNull - private final PostDetailResponseDto postDetail; - - @Schema(description = "차단된 유저의 게시물인지 여부", example = "false") - @NotNull - private final boolean isBlockedPost; -} \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java index fe7c0d5c..dd38fa9b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java @@ -14,11 +14,10 @@ @Schema(name = "PostV2GetPostsResponseDto", description = "게시글 조회 응답 Dto") public class PostV2GetPostsResponseDto { - @Schema(description = "게시글의 기본 정보 + 댓글 썸네일 이미지 리스트 정보 + 차단된 유저의 게시물인지 아닌지 정보를 담고 있는 DTO") + @Schema(description = "게시글 객체", example = "") @NotNull - private final List posts; + private final List posts; - @Schema(description = "페이지 메타 정보") @NotNull private final PageMetaDto meta; diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java index 3b073371..4e7da2c4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java @@ -14,21 +14,21 @@ public interface PostV2Service { - PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId); + PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId); - PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId); + PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId); - PostDetailBaseDto getPost(Integer userId, Integer postId); + PostDetailBaseDto getPost(Integer userId, Integer postId); - void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId); + void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId); - PostV2GetPostCountResponseDto getPostCount(Integer meetingId); + PostV2GetPostCountResponseDto getPostCount(Integer meetingId); - void deletePost(Integer postId, Integer userId); + void deletePost(Integer postId, Integer userId); - PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId); + PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId); - PostV2ReportResponseDto reportPost(Integer postId, Integer userId); + PostV2ReportResponseDto reportPost(Integer postId, Integer userId); - PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer userId); + PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java index f1d6acd9..41fad4b2 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java @@ -1,12 +1,16 @@ package org.sopt.makers.crew.main.post.v2.service; -import static java.util.stream.Collectors.*; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; -import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.*; +import static java.util.stream.Collectors.toList; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.ALREADY_REPORTED_POST; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FORBIDDEN_EXCEPTION; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.MAX_IMAGE_UPLOAD_EXCEEDED; +import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE; +import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_PUSH_NOTIFICATION_TITLE; +import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.PUSH_NOTIFICATION_CATEGORY; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; + +import lombok.RequiredArgsConstructor; import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.common.exception.ForbiddenException; @@ -29,7 +33,6 @@ import org.sopt.makers.crew.main.entity.report.ReportRepository; import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.entity.user.UserRepository; -import org.sopt.makers.crew.main.external.playground.service.MemberBlockService; import org.sopt.makers.crew.main.internal.notification.PushNotificationService; import org.sopt.makers.crew.main.internal.notification.dto.PushNotificationRequestDto; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; @@ -38,22 +41,19 @@ import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailResponseDto; -import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailWithBlockStatusResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; -import org.sopt.makers.crew.main.user.v2.service.UserV2Service; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import lombok.RequiredArgsConstructor; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -68,8 +68,6 @@ public class PostV2ServiceImpl implements PostV2Service { private final ReportRepository reportRepository; private final PushNotificationService pushNotificationService; - private final MemberBlockService memberBlockService; - private final UserV2Service userV2Service; @Value("${push-notification.web-url}") private String pushWebUrl; @@ -84,14 +82,16 @@ public class PostV2ServiceImpl implements PostV2Service { */ @Override @Transactional - public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId) { + public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, + Integer userId) { Meeting meeting = meetingRepository.findByIdOrThrow(requestBody.getMeetingId()); User user = userRepository.findByIdOrThrow(userId); List applies = applyRepository.findAllByMeetingId(meeting.getId()); boolean isInMeeting = applies.stream() - .anyMatch(apply -> apply.getUserId().equals(userId) && apply.getStatus().equals(EnApplyStatus.APPROVE)); + .anyMatch(apply -> apply.getUserId().equals(userId) + && apply.getStatus().equals(EnApplyStatus.APPROVE)); boolean isMeetingCreator = meeting.getUserId().equals(userId); @@ -109,32 +109,27 @@ public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBod Post savedPost = postRepository.save(post); - List userIdList = applyRepository.findAllByMeetingIdAndStatus(meeting.getId(), EnApplyStatus.APPROVE) + List userIdList = applyRepository.findAllByMeetingIdAndStatus(meeting.getId(), + EnApplyStatus.APPROVE) .stream() .map(apply -> String.valueOf(apply.getUser().getOrgId())) .collect(toList()); String[] userIds = userIdList.toArray(new String[0]); - String pushNotificationContent = String.format("[%s의 새 글] : \"%s\"", user.getName(), post.getTitle()); + String pushNotificationContent = String.format("[%s의 새 글] : \"%s\"", + user.getName(), post.getTitle()); String pushNotificationWeblink = pushWebUrl + "/detail?id=" + meeting.getId(); PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of(userIds, - NEW_POST_PUSH_NOTIFICATION_TITLE.getValue(), pushNotificationContent, PUSH_NOTIFICATION_CATEGORY.getValue(), - pushNotificationWeblink); + NEW_POST_PUSH_NOTIFICATION_TITLE.getValue(), + pushNotificationContent, + PUSH_NOTIFICATION_CATEGORY.getValue(), pushNotificationWeblink); pushNotificationService.sendPushNotification(pushRequestDto); return PostV2CreatePostResponseDto.of(savedPost.getId()); } - /** - * 모일 게시글 리스트 페이지네이션 조회 (12개) - * - * @param queryCommand 게시글 조회를 위한 쿼리 명령 객체 - * @param userId 게시글을 조회하는 사용자 id - * @return 게시글 정보(게시글 객체 + 댓글 단 사람의 썸네일 + 차단된 유저의 게시물 여부)와 페이지 메타 정보를 포함한 응답 DTO - * @apiNote 사용자가 차단한 유저의 게시물은 해당 게시물에 대한 차단 여부를 함께 반환 - */ @Override @Transactional(readOnly = true) public PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId) { @@ -144,29 +139,10 @@ public PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Inte PageOptionsDto pageOptionsDto = new PageOptionsDto(meetingPostListDtos.getPageable().getPageNumber() + 1, meetingPostListDtos.getPageable().getPageSize()); - PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, (int)meetingPostListDtos.getTotalElements()); + PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, + (int)meetingPostListDtos.getTotalElements()); - // 조회된 게시물의 작성자들의 플그 ID 리스트 가져오기 - List userOrgIds = meetingPostListDtos.getContent() - .stream() - .map(postDetail -> postDetail.getUser().getOrgId().longValue()) - .collect(Collectors.toList()); - - User user = userV2Service.getUserByUserId(userId); - Long orgId = user.getOrgId().longValue(); - - // 한 번의 호출로 현재 유저(차단자)가 위 플그 ID에 대한 차단 여부를 확인 - Map blockedPostMap = memberBlockService.getBlockedUsers(orgId, userOrgIds); - - List responseDtos = meetingPostListDtos.getContent() - .stream() - .map(postDetail -> { - boolean isBlockedPost = blockedPostMap.getOrDefault(postDetail.getUser().getOrgId().longValue(), false); - return PostDetailWithBlockStatusResponseDto.of(postDetail, isBlockedPost); - }) - .collect(Collectors.toList()); - - return PostV2GetPostsResponseDto.of(responseDtos, pageMetaDto); + return PostV2GetPostsResponseDto.of(meetingPostListDtos.getContent(), pageMetaDto); } /** @@ -186,14 +162,21 @@ public void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Int User user = userRepository.findByIdOrThrow(userId); Post post = postRepository.findByIdOrThrow(requestBody.getPostId()); - String pushNotificationContent = String.format("[%s의 글] : \"%s\"", user.getName(), post.getTitle()); + String pushNotificationContent = String.format("[%s의 글] : \"%s\"", + user.getName(), post.getTitle()); String pushNotificationWeblink = pushWebUrl + "/post?id=" + post.getId(); - String[] userOrgIds = requestBody.getOrgIds().stream().map(Object::toString).toArray(String[]::new); + String[] userOrgIds = requestBody.getOrgIds().stream() + .map(Object::toString) + .toArray(String[]::new); - PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of(userOrgIds, - NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE.getValue(), pushNotificationContent, - PUSH_NOTIFICATION_CATEGORY.getValue(), pushNotificationWeblink); + PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of( + userOrgIds, + NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE.getValue(), + pushNotificationContent, + PUSH_NOTIFICATION_CATEGORY.getValue(), + pushNotificationWeblink + ); pushNotificationService.sendPushNotification(pushRequestDto); } @@ -252,7 +235,8 @@ public PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBo post.updatePost(requestBody.getTitle(), requestBody.getContents(), requestBody.getImages()); return PostV2UpdatePostResponseDto.of(post.getId(), post.getTitle(), post.getContents(), - String.valueOf(time.now()), post.getImages()); + String.valueOf(time.now()), + post.getImages()); } /** @@ -274,7 +258,11 @@ public PostV2ReportResponseDto reportPost(Integer postId, Integer userId) { throw new BadRequestException(ALREADY_REPORTED_POST.getErrorCode()); } - Report report = Report.builder().post(post).postId(postId).userId(userId).build(); + Report report = Report.builder() + .post(post) + .postId(postId) + .userId(userId) + .build(); Report savedReport = reportRepository.save(report); @@ -300,7 +288,10 @@ public PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer us // 취소된 좋아요 정보가 없을 경우 if (deletedLikes == 0) { - Like newLike = Like.builder().postId(postId).userId(userId).build(); + Like newLike = Like.builder() + .postId(postId) + .userId(userId) + .build(); likeRepository.save(newLike); diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java index deb1fb9b..1b71514d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java @@ -2,7 +2,6 @@ import java.util.List; -import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAppliedMeetingByUserResponseDto; @@ -21,5 +20,5 @@ public interface UserV2Service { UserV2GetCreatedMeetingByUserResponseDto getCreatedMeetingByUser(Integer userId); - User getUserByUserId(Integer userId); + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java index 17e792c5..6bdc648d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java @@ -5,6 +5,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import lombok.RequiredArgsConstructor; + import org.sopt.makers.crew.main.common.exception.BaseException; import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.apply.Applies; @@ -26,8 +28,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import lombok.RequiredArgsConstructor; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -45,12 +45,19 @@ public List getAllMeetingByUser(Integer use List myMeetings = meetingRepository.findAllByUserId(user.getId()); - List userJoinedList = Stream.concat(myMeetings.stream(), + List userJoinedList = Stream.concat( + myMeetings.stream(), applyRepository.findAllByUserIdAndStatus(userId, EnApplyStatus.APPROVE) .stream() - .map(apply -> apply.getMeeting())) - .map(meeting -> UserV2GetAllMeetingByUserMeetingDto.of(meeting.getId(), meeting.getTitle(), - meeting.getDesc(), meeting.getImageURL().get(0).getUrl(), meeting.getCategory().getValue())) + .map(apply -> apply.getMeeting()) + ) + .map(meeting -> UserV2GetAllMeetingByUserMeetingDto.of( + meeting.getId(), + meeting.getTitle(), + meeting.getDesc(), + meeting.getImageURL().get(0).getUrl(), + meeting.getCategory().getValue() + )) .sorted(Comparator.comparing(UserV2GetAllMeetingByUserMeetingDto::getId).reversed()) .collect(Collectors.toList()); @@ -103,16 +110,12 @@ public UserV2GetAppliedMeetingByUserResponseDto getAppliedMeetingByUser(Integer Applies allApplies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); List appliedMeetingByUserDtos = myApplies.stream() - .map(apply -> ApplyV2GetAppliedMeetingByUserResponseDto.of(apply.getId(), apply.getStatus().getValue(), + .map(apply -> ApplyV2GetAppliedMeetingByUserResponseDto.of( + apply.getId(), apply.getStatus().getValue(), MeetingV2GetCreatedMeetingByUserResponseDto.of(apply.getMeeting(), apply.getMeeting().getUser(), - allApplies.getApprovedCount(apply.getMeetingId()), time.now()))) - .toList(); + allApplies.getApprovedCount(apply.getMeetingId()), time.now()) + )).toList(); return UserV2GetAppliedMeetingByUserResponseDto.of(appliedMeetingByUserDtos); } - - @Override - public User getUserByUserId(Integer userId) { - return userRepository.findByIdOrThrow(userId); - } } diff --git a/main/src/main/resources/application-test.yml b/main/src/main/resources/application-test.yml index ad43358a..a0e60eb0 100644 --- a/main/src/main/resources/application-test.yml +++ b/main/src/main/resources/application-test.yml @@ -1,3 +1,5 @@ + + server: port: 4000 @@ -8,16 +10,15 @@ spring: import: application-secret.properties datasource: driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - jdbc-url: jdbc:tc:postgresql:///crew - - playground-datasource: - jdbc-url: jdbc:tc:postgresql:///playground + url: jdbc:tc:postgresql:///test-database + # username: root + # password: jpa: open-in-view: false hibernate: naming: physical-strategy: org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy - ddl-auto: none + ddl-auto: create-drop properties: hibernate: show_sql: true @@ -63,7 +64,7 @@ push-notification: push-server-url: ${DEV_PUSH_SERVER_URL} notice: - secret-key: ${NOTICE_SECRET_KEY} + secret-key : ${NOTICE_SECRET_KEY} playground: server: @@ -93,9 +94,4 @@ management: prometheus: metrics: export: - enabled: true - -logging: - level: - org.hibernate.SQL: DEBUG # Hibernate의 SQL 쿼리 로그 레벨 설정 - org.hibernate.type: TRACE # SQL 파라미터 바인딩을 보기 위한 타입 정보 로그 출력 + enabled: true \ No newline at end of file diff --git a/main/src/main/resources/schema.sql b/main/src/main/resources/schema.sql deleted file mode 100644 index 9b2d7880..00000000 --- a/main/src/main/resources/schema.sql +++ /dev/null @@ -1,201 +0,0 @@ -DROP TYPE IF EXISTS meeting_joinableparts_enum; - -create type meeting_joinableparts_enum as enum ('PM', 'DESIGN', 'IOS', 'ANDROID', 'SERVER', 'WEB'); - -create table if not exists "user" -( - id serial - constraint "PK_cace4a159ff9f2512dd42373760" - primary key - constraint "UQ_cace4a159ff9f2512dd42373760" - unique, - name varchar not null, - "orgId" integer not null, - "profileImage" varchar, - activities jsonb, - phone varchar -); - --- alter table "user" --- owner to songmingyu; - -create table if not exists meeting -( - id serial - constraint "PK_dccaf9e4c0e39067d82ccc7bb83" - primary key - constraint "UQ_dccaf9e4c0e39067d82ccc7bb83" - unique, - "userId" integer not null - constraint "FK_854982a74818bb6307419e0e6b8" - references "user" - on delete cascade, - title varchar not null, - category varchar not null, - "imageURL" jsonb not null, - "startDate" timestamp not null, - "endDate" timestamp not null, - capacity integer not null, - "desc" varchar not null, - "processDesc" varchar not null, - "mStartDate" timestamp not null, - "mEndDate" timestamp not null, - "leaderDesc" varchar not null, - "targetDesc" varchar not null, - note varchar, - "isMentorNeeded" boolean not null, - "canJoinOnlyActiveGeneration" boolean not null, - "targetActiveGeneration" integer, - "joinableParts" meeting_joinableparts_enum[], - "createdGeneration" integer default 32 -); - -create table if not exists apply -( - id serial - constraint "PK_c61ed680472aa0f58499175d902" - primary key, - type integer default 0 not null, - "meetingId" integer not null - constraint "FK_b130d23f4642d1ef51c6e54d257" - references meeting - on delete cascade, - "userId" integer not null - constraint "FK_359c8244808809db5ee96ed066e" - references "user" - on delete cascade, - content varchar not null, - "appliedDate" timestamp not null, - status integer default 0 not null -); - --- alter table apply --- owner to songmingyu; - -create index if not exists "meetingId_index" - on apply ("meetingId"); - -create index if not exists "userId_index" - on apply ("userId"); - -create table if not exists notice -( - id serial - constraint "PK_705062b14410ff1a04998f86d72" - primary key - constraint "UQ_705062b14410ff1a04998f86d72" - unique, - title varchar not null, - "subTitle" varchar not null, - contents varchar not null, - "createdDate" timestamp not null, - "exposeStartDate" timestamp not null, - "exposeEndDate" timestamp not null -); - -create table if not exists post -( - id serial - constraint "PK_be5fda3aac270b134ff9c21cdee" - primary key - constraint "UQ_be5fda3aac270b134ff9c21cdee" - unique, - contents varchar not null, - "createdDate" timestamp not null, - "updatedDate" timestamp not null, - "likeCount" integer default 0 not null, - "userId" integer not null - constraint "FK_5c1cf55c308037b5aca1038a131" - references "user", - "meetingId" integer not null - constraint "FK_85e980cf9166f5337c0b2b76bc0" - references meeting, - title varchar not null, - "viewCount" integer default 0 not null, - images text[], - "commentCount" integer default 0 not null -); - --- alter table post --- owner to songmingyu; - -create table if not exists comment -( - id serial - constraint "PK_0b0e4bbc8415ec426f87f3a88e2" - primary key - constraint "UQ_0b0e4bbc8415ec426f87f3a88e2" - unique, - contents varchar not null, - depth integer default 0 not null, - "order" integer default 0 not null, - "createdDate" timestamp not null, - "updatedDate" timestamp not null, - "likeCount" integer default 0 not null, - "userId" integer - constraint "FK_c0354a9a009d3bb45a08655ce3b" - references "user", - "postId" integer not null - constraint "FK_94a85bb16d24033a2afdd5df060" - references post - on delete cascade, - "parentId" integer -); - -create table if not exists "like" -( - id serial - constraint "PK_eff3e46d24d416b52a7e0ae4159" - primary key - constraint "UQ_eff3e46d24d416b52a7e0ae4159" - unique, - "createdDate" timestamp not null, - "userId" integer not null - constraint "FK_e8fb739f08d47955a39850fac23" - references "user", - "postId" integer - constraint "FK_3acf7c55c319c4000e8056c1279" - references post - on delete cascade, - "commentId" integer - constraint "FK_d86e0a3eeecc21faa0da415a18a" - references comment - on delete cascade -); - -create table if not exists report -( - id serial - constraint "PK_99e4d0bea58cba73c57f935a546" - primary key - constraint "UQ_99e4d0bea58cba73c57f935a546" - unique, - "createdDate" timestamp not null, - "commentId" integer - constraint "FK_97372830f2390803a3e2df4a46e" - references comment, - "userId" integer not null - constraint "FK_e347c56b008c2057c9887e230aa" - references "user" - on delete cascade, - "postId" integer - constraint "FK_4b6fe2df37305bc075a4a16d3ea" - references post - on delete cascade -); - -create table if not exists advertisement -( - id serial - primary key, - "advertisementLink" varchar(255), - "advertisementEndDate" timestamp(6), - "advertisementStartDate" timestamp(6), - priority bigint, - "advertisementCategory" varchar(255) - constraint "advertisement_advertisementCategory_check" - check (("advertisementCategory")::text = ANY -((ARRAY ['POST'::character varying, 'MEETING'::character varying])::text[])), - "advertisementDesktopImageUrl" varchar(255), - "advertisementMobileImageUrl" varchar(255) - ); \ No newline at end of file diff --git a/main/src/test/java/org/sopt/makers/crew/main/entity/user/UserRepositoryTest.java b/main/src/test/java/org/sopt/makers/crew/main/entity/user/UserRepositoryTest.java index 8251eb72..21d5a8b6 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/entity/user/UserRepositoryTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/entity/user/UserRepositoryTest.java @@ -2,19 +2,17 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; +import org.sopt.makers.crew.main.common.config.TestConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.context.jdbc.SqlGroup; -import org.testcontainers.junit.jupiter.Testcontainers; -@SpringBootTest -@Testcontainers +@DataJpaTest @ActiveProfiles("test") -@SqlGroup({ - @Sql(value = "/sql/delete-all-data.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) -}) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Import(TestConfig.class) public class UserRepositoryTest { @Autowired @@ -27,10 +25,9 @@ public class UserRepositoryTest { // when User savedUser = userRepository.save(user); - User foundUser = userRepository.findByIdOrThrow(savedUser.getId()); // then - Assertions.assertThat(foundUser) + Assertions.assertThat(savedUser) .extracting("name", "phone") .containsExactly(user.getName(), user.getPhone()); } diff --git a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/repository/ApplyRepositoryTest.java b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/repository/ApplyRepositoryTest.java index e261317e..3b961566 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/repository/ApplyRepositoryTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/repository/ApplyRepositoryTest.java @@ -14,18 +14,18 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyInfoDto; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.SqlGroup; -import org.testcontainers.junit.jupiter.Testcontainers; -@SpringBootTest +@DataJpaTest @ActiveProfiles("test") -@Testcontainers +@Import(TestConfig.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @SqlGroup({ @Sql(value = "/sql/apply-repository-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD), diff --git a/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java b/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java index 2783bbe8..b677c9fa 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java @@ -1,143 +1,146 @@ package org.sopt.makers.crew.main.post.v2.repository; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; - import org.junit.jupiter.api.Test; +import org.sopt.makers.crew.main.common.config.TestConfig; import org.sopt.makers.crew.main.entity.post.PostRepository; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailResponseDto; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.SqlGroup; -import org.testcontainers.junit.jupiter.Testcontainers; -@SpringBootTest +@DataJpaTest @ActiveProfiles("test") -@Testcontainers +@Import(TestConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @SqlGroup({ - @Sql(value = "/sql/post-repository-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD), - @Sql(value = "/sql/delete-all-data.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD) + @Sql(value = "/sql/post-repository-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD), + @Sql(value = "/sql/delete-all-data.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD) }) public class PostRepositoryTest { - @Autowired - private PostRepository postRepository; - - @Test - void 모임_ID로_필터링해서_게시글_목록_조회() { - // given - int page = 1; - int take = 9; - Integer meetingId = 1; - PostGetPostsCommand queryCommand = new PostGetPostsCommand(meetingId, page, take); - Integer userId = 1; - - // when - Page postDetailJsonDtos = postRepository.findPostList(queryCommand, - PageRequest.of(page - 1, take), userId); - - // then - assertThat(postDetailJsonDtos.getTotalElements()).isEqualTo(3); - assertThat(postDetailJsonDtos.getSize()).isEqualTo(take); - assertThat(postDetailJsonDtos.getNumber()).isEqualTo(page - 1); - assertThat(postDetailJsonDtos.getTotalPages()).isEqualTo(1); - - PostDetailResponseDto postDetailDto1 = postDetailJsonDtos.getContent().get(0); - assertThat(postDetailDto1) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(3, "제목3", "내용3", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 2, 0)), - 0, false, 0); - assertThat(postDetailDto1.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(1, "스터디 구합니다1", "행사"); - - PostDetailResponseDto postDetailDto2 = postDetailJsonDtos.getContent().get(1); - assertThat(postDetailDto2) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(2, "제목2", "내용2", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 1, 0)), - 0, false, 0); - assertThat(postDetailDto2.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(1, "스터디 구합니다1", "행사"); - - PostDetailResponseDto postDetailDto3 = postDetailJsonDtos.getContent().get(2); - assertThat(postDetailDto3) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(1, "제목1", "내용1", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 0, 0)), - 2, true, 5); - assertThat(postDetailDto3.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(1, "스터디 구합니다1", "행사"); - // 댓글 작성자 최대 3명 순서 검증 - assertThat(postDetailDto3.getCommenterThumbnails()) - .containsExactly("profile1.jpg", "profile2.jpg", "profile3.jpg"); - } - - @Test - void 게시글_목록_전체_조회() { - // given - int page = 1; - int take = 9; - PostGetPostsCommand queryCommand = new PostGetPostsCommand(null, page, take); - Integer userId = 1; - - // when - Page postDetailJsonDtos = postRepository.findPostList(queryCommand, - PageRequest.of(page - 1, take), - userId); - - // then - assertThat(postDetailJsonDtos.getTotalElements()).isEqualTo(5); - assertThat(postDetailJsonDtos.getSize()).isEqualTo(take); - assertThat(postDetailJsonDtos.getNumber()).isEqualTo(page - 1); - assertThat(postDetailJsonDtos.getTotalPages()).isEqualTo(1); - - PostDetailResponseDto postDetailDto1 = postDetailJsonDtos.getContent().get(0); - assertThat(postDetailDto1) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(5, "제목5", "내용5", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 4, 0)), - 0, false, 0); - assertThat(postDetailDto1.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(2, "스터디 구합니다2", "스터디"); - - PostDetailResponseDto postDetailDto2 = postDetailJsonDtos.getContent().get(4); - assertThat(postDetailDto2) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(1, "제목1", "내용1", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 0, 0)), - 2, true, 5); - assertThat(postDetailDto2.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(1, "스터디 구합니다1", "행사"); - } - - @Test - void 게시글_개수_조회() { - // given - int meetingId1 = 1; - int meetingId2 = 2; - - // when - Integer postCount1 = postRepository.countByMeetingId(meetingId1); - Integer postCount2 = postRepository.countByMeetingId(meetingId2); - - // then - assertThat(postCount1).isEqualTo(3); - assertThat(postCount2).isEqualTo(2); - } + @Autowired + private PostRepository postRepository; + + @Test + void 모임_ID로_필터링해서_게시글_목록_조회() { + // given + int page = 1; + int take = 9; + Integer meetingId = 1; + PostGetPostsCommand queryCommand = new PostGetPostsCommand(meetingId, page, take); + Integer userId = 1; + + // when + Page postDetailJsonDtos = postRepository.findPostList(queryCommand, + PageRequest.of(page - 1, take), + userId); + + // then + assertThat(postDetailJsonDtos.getTotalElements()).isEqualTo(3); + assertThat(postDetailJsonDtos.getSize()).isEqualTo(take); + assertThat(postDetailJsonDtos.getNumber()).isEqualTo(page - 1); + assertThat(postDetailJsonDtos.getTotalPages()).isEqualTo(1); + + PostDetailResponseDto postDetailDto1 = postDetailJsonDtos.getContent().get(0); + assertThat(postDetailDto1) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(3, "제목3", "내용3", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 2, 0)), + 0, false, 0); + assertThat(postDetailDto1.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(1, "스터디 구합니다1", "행사"); + + PostDetailResponseDto postDetailDto2 = postDetailJsonDtos.getContent().get(1); + assertThat(postDetailDto2) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(2, "제목2", "내용2", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 1, 0)), + 0, false, 0); + assertThat(postDetailDto2.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(1, "스터디 구합니다1", "행사"); + + PostDetailResponseDto postDetailDto3 = postDetailJsonDtos.getContent().get(2); + assertThat(postDetailDto3) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(1, "제목1", "내용1", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 0, 0)), + 2, true, 5); + assertThat(postDetailDto3.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(1, "스터디 구합니다1", "행사"); + // 댓글 작성자 최대 3명 순서 검증 + assertThat(postDetailDto3.getCommenterThumbnails()) + .containsExactly("profile1.jpg", "profile2.jpg", "profile3.jpg"); + } + + @Test + void 게시글_목록_전체_조회() { + // given + int page = 1; + int take = 9; + PostGetPostsCommand queryCommand = new PostGetPostsCommand(null, page, take); + Integer userId = 1; + + // when + Page postDetailJsonDtos = postRepository.findPostList(queryCommand, + PageRequest.of(page - 1, take), + userId); + + // then + assertThat(postDetailJsonDtos.getTotalElements()).isEqualTo(5); + assertThat(postDetailJsonDtos.getSize()).isEqualTo(take); + assertThat(postDetailJsonDtos.getNumber()).isEqualTo(page - 1); + assertThat(postDetailJsonDtos.getTotalPages()).isEqualTo(1); + + PostDetailResponseDto postDetailDto1 = postDetailJsonDtos.getContent().get(0); + assertThat(postDetailDto1) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(5, "제목5", "내용5", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 4, 0)), + 0, false, 0); + assertThat(postDetailDto1.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(2, "스터디 구합니다2", "스터디"); + + PostDetailResponseDto postDetailDto2 = postDetailJsonDtos.getContent().get(4); + assertThat(postDetailDto2) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(1, "제목1", "내용1", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 0, 0)), + 2, true, 5); + assertThat(postDetailDto2.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(1, "스터디 구합니다1", "행사"); + } + + @Test + void 게시글_개수_조회() { + // given + int meetingId1 = 1; + int meetingId2 = 2; + + // when + Integer postCount1 = postRepository.countByMeetingId(meetingId1); + Integer postCount2 = postRepository.countByMeetingId(meetingId2); + + // then + assertThat(postCount1).isEqualTo(3); + assertThat(postCount2).isEqualTo(2); + } } diff --git a/main/src/test/resources/sql/apply-repository-test-data.sql b/main/src/test/resources/sql/apply-repository-test-data.sql index 00c2525e..3ddff246 100644 --- a/main/src/test/resources/sql/apply-repository-test-data.sql +++ b/main/src/test/resources/sql/apply-repository-test-data.sql @@ -1,3 +1,5 @@ +CREATE TYPE meeting_joinableparts_enum AS ENUM ('PM', 'DESIGN', 'IOS', 'ANDROID', 'SERVER', 'WEB'); + INSERT INTO "user" (id, name, "orgId", activities, "profileImage", phone) VALUES (1, '김삼순', 1001, '[{"part": "서버", "generation": 33}, {"part": "iOS", "generation": 32}]', @@ -12,6 +14,30 @@ VALUES (1, '김삼순', 1001, '[{"part": "iOS", "generation": 32}, {"part": "안드로이드", "generation": 29}]', 'profile4.jpg', '010-5555-5555'); +create table "meeting" ( + "canJoinOnlyActiveGeneration" boolean not null, + "capacity" integer not null, + "createdGeneration" integer not null, + "id" serial not null, + "isMentorNeeded" boolean not null, + "targetActiveGeneration" integer, + "userId" integer, + "endDate" TIMESTAMP not null, + "mEndDate" TIMESTAMP not null, + "mStartDate" TIMESTAMP not null, + "startDate" TIMESTAMP not null, + "category" varchar(255) not null, + "desc" varchar(255) not null, + "leaderDesc" varchar(255) not null, + "note" varchar(255), + "processDesc" varchar(255) not null, + "targetDesc" varchar(255) not null, + "title" varchar(255) not null, + "imageURL" jsonb, + "joinableParts" meeting_joinableparts_enum[], + primary key ("id") +); + INSERT INTO meeting (id, "userId", title, category, "imageURL", "startDate", "endDate", capacity, "desc", "processDesc", "mStartDate", "mEndDate", "leaderDesc", "targetDesc", note, "isMentorNeeded", "canJoinOnlyActiveGeneration", "createdGeneration", diff --git a/main/src/test/resources/sql/delete-all-data.sql b/main/src/test/resources/sql/delete-all-data.sql index 493bd583..637ccb04 100644 --- a/main/src/test/resources/sql/delete-all-data.sql +++ b/main/src/test/resources/sql/delete-all-data.sql @@ -2,9 +2,9 @@ DELETE FROM "apply"; DELETE FROM "comment"; DELETE FROM "like"; -DELETE FROM "post"; DELETE FROM "meeting"; DELETE FROM "notice"; +DELETE FROM "post"; DELETE FROM "user"; -- 시퀀스 초기화 diff --git a/main/src/test/resources/sql/post-repository-test-data.sql b/main/src/test/resources/sql/post-repository-test-data.sql index f381c0bb..e2676f94 100644 --- a/main/src/test/resources/sql/post-repository-test-data.sql +++ b/main/src/test/resources/sql/post-repository-test-data.sql @@ -1,3 +1,5 @@ +CREATE TYPE meeting_joinableparts_enum AS ENUM ('PM', 'DESIGN', 'IOS', 'ANDROID', 'SERVER', 'WEB'); + INSERT INTO "user" (id, name, "orgId", activities, "profileImage", phone) VALUES (1, '김삼순', 1001, '[{"part": "서버", "generation": 33}, {"part": "iOS", "generation": 32}]', @@ -12,6 +14,30 @@ VALUES (1, '김삼순', 1001, '[{"part": "iOS", "generation": 32}, {"part": "안드로이드", "generation": 29}]', 'profile4.jpg', '010-5555-5555'); +create table "meeting" ( + "canJoinOnlyActiveGeneration" boolean not null, + "capacity" integer not null, + "createdGeneration" integer not null, + "id" serial not null, + "isMentorNeeded" boolean not null, + "targetActiveGeneration" integer, + "userId" integer, + "endDate" TIMESTAMP not null, + "mEndDate" TIMESTAMP not null, + "mStartDate" TIMESTAMP not null, + "startDate" TIMESTAMP not null, + "category" varchar(255) not null, + "desc" varchar(255) not null, + "leaderDesc" varchar(255) not null, + "note" varchar(255), + "processDesc" varchar(255) not null, + "targetDesc" varchar(255) not null, + "title" varchar(255) not null, + "imageURL" jsonb, + "joinableParts" meeting_joinableparts_enum[], + primary key ("id") +); + INSERT INTO meeting (id, "userId", title, category, "imageURL", "startDate", "endDate", capacity, "desc", "processDesc", "mStartDate", "mEndDate", "leaderDesc", "targetDesc", note, "isMentorNeeded", "canJoinOnlyActiveGeneration", "createdGeneration", From 754c5c4b3c288bd1cde7a6a81f93315fc5d9a1a2 Mon Sep 17 00:00:00 2001 From: DongHoon Lee <125895298+hoonyworld@users.noreply.github.com> Date: Sun, 22 Sep 2024 15:10:23 +0900 Subject: [PATCH 3/9] =?UTF-8?q?=08refactor:=20=ED=94=BC=EB=93=9C(post)=20?= =?UTF-8?q?=EC=B0=A8=EB=8B=A8=20=EA=B8=B0=EB=8A=A5=20FE=20=EC=9D=98?= =?UTF-8?q?=EA=B2=AC=20=EB=B0=98=EC=98=81=20(#388)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(PostDetailBaseDto): 불필요한 import문 삭제 * feat(PostDetailResponseDto): 차단된 유저의 게시물인지 여부를 나타내는 필드 추가 * feat(UserV2Service): id를 통해 User 객체를 가져오는 메서드 선언 * feat(UserV2ServiceImpl): id를 통해 User 객체를 가져오는 메서드 구현 * refactor: 매개변수로 userId가 아닌 user 객체를 받도록 변경 * feat(PostSearchRepositoryImpl): 차단된 유저의 게시물 여부를 확인하는 서브쿼리 추가 * test(PostRepositoryTest): 테스트 임시 비활성화 * feat(PostDetailWithBlockStatusResponseDto): 게시글 객체 + 차단된 유저의 게시물 여부를 나타내는 응답 DTO 생성 * refactor(PostV2GetPostsResponseDto): DTO 객체를 PostDetailResponseDto에서 PostDetailWithBlockStatusResponseDto로 수정 * chore(PostDetailBaseDto): description 상세화 * refactor(PostDetailResponseDto): isBlockedPost 필드 삭제 및 description 상세화 * refactor(PostSearchRepositoryImpl): 차단된 유저의 게시물 여부 확인 서브쿼리 삭제 * feat(MemberBlockSearchRepository): 특정 사용자가 다른 사용자를 차단했는지 여부를 확인하는 메서드 선언 * refactor(MemberBlockRepository): 커스텀 쿼리를 메서드를 구현하기 위한 MemberBlockSearchRepository상속 * feat(MemberBlockRepositoryImpl): 회원 차단 여부 확인을 위한 쿼리 메서드 추가 * feat(MemberBlockService): 회원 차단 여부 확인을 위한 MemberBlockService 인터페이스 정의 * feat(MemberBlockServiceImpl): 회원 차단 여부 확인메서드 구현 * refactor(PostV2ServiceImpl):회원 차단 여부를 DTO로 넘겨주도록 변경 * feat(JpaAuditingConfig): multi-datasource 환경을 위한 JPAQueryFactory 설정 추가 - primaryEntityManager와 playgroundEntityManager를 위한 JPAQueryFactory 빈 등록 - QueryDSL을 사용하여 다양한 데이터 소스에 대한 쿼리 수행 가능 * fix(MemberBlockRepositoryImpl): playgroundQueryFactory를 주입하여 다중 데이터소스 지원 * refactor(MemberBlockSearchRepository): 차단자가 특정 차단된 사용자 목록에 대해 각각의 차단 상태를 확인하는 메서드로 변경 * refactor(MemberBlockRepositoryImpl): 단일 쿼리로 차단자가 특정 차단된 사용자 목록에 대해 각각의 차단 상태를 확인하는 메서드 구현 * refactor(MemberBlockService): 차단자가 특정 차단된 사용자 목록에 대해 각각의 차단 상태 정보를 얻을 수 있는 메서드 선언 * refactor(MemberBlockServiceImpl): 차단자가 특정 차단된 사용자 목록에 대해 각각의 차단 상태 정보를 얻을 수 있는 메서드 구현 * rename(MemberBlockSearchRepositoryImpl): MemberBlockRepositoryImpl에서 MemberBlockSearchRepositoryImpl로 클래스 이름 변경 * refactor: 매개변수 이름 변경 * refactor(PostV2ServiceImpl): 조회된 게시물 작성자의 orgId 리스트를 수집하여 차단 여부를 확인하는 로직 최적화 * rename(PostDetailWithBlockStatusResponseDto): 응답 필드명 변경 * chore: naver 코드 포맷터 적 * refactor: 매개변수를 userId로 받도록 변경 * refactor: osiv 관련 코드리뷰 반 * test: test yml 수정 * chore(config): test 설정 추가, 테스트 프로필일 경우 schema.sql 실행 * test(config): 테스트컨테이너 사용하도록 코드 전면 수정 * chore(MemberBlock): [중요] querydsl -> spring data jpa 변경으로 인한 임시 수정 * refactor: 역할에 맞게 서비스 레이어에서 Map으로 변환하도록 리팩토링 * test(PostRepositoryTest): 주석 해제 * delete: querydsl 대신 spring data jpa를 사용하기 위해 삭제 - spring data jpa의 메서드 네이밍이 복잡하지 않으며 성능차이도 비슷하기 때문에 코드를 줄이는 방향으로 결정 * feat(MemberBlockRepository): 차단자의 orgId로 차단된 모든 유저의 정보를 조회하는 메서드 추가 * refactor(MemberBlockServiceImpl): spring data jpa를 이용해 차단된 유저의 정보를 Map으로 반환하도록 변경 * chore(application-test.yml): 테스트 컨테이너 jdbc-url 변경 * refactor(PostDetailResponseDto): isBlockedPost 필드 추가 * refactor(PostSearchRepositoryImpl): 정적팩토리 메서드 호출 시 isBlockedPost 필드를 임시로 null로 초기화 * refactor(PostV2GetPostsResponseDto): PostDetailResponseDto 객체 리스트 필드로 변경 * refactor(PostV2ServiceImpl): PostDetailResponseDto 객체로 변경 및 isBlockedPost를 true/false로 초기화하도록 변경 * refactor(PostSearchRepositoryImpl): isBlockedPost를 false로 초기화하도록 변경 * refactor(PostDetailResponseDto): isBlockedPost 자료형을 널값을 허용하지 않는 boolean으로 변경 * delete(PostDetailWithBlockStatusResponseDto): PostDetailWithBlockStatusResponseDto 삭제 * refactor(PostV2ServiceImpl): DTO 반환 로직 private 메서드로 분리 --------- Co-authored-by: mikekks --- main/build.gradle | 1 + .../common/config/CrewDatabaseConfig.java | 39 ++- .../main/common/config/JpaAuditingConfig.java | 31 ++- .../config/PlaygroundDataSourceConfig.java | 11 +- .../entity/post/PostSearchRepository.java | 4 +- .../entity/post/PostSearchRepositoryImpl.java | 112 ++++---- .../member_block/MemberBlockRepository.java | 5 +- .../service/MemberBlockService.java | 8 + .../service/MemberBlockServiceImpl.java | 27 ++ .../makers/crew/main/post/v2/PostV2Api.java | 128 ++++------ .../crew/main/post/v2/PostV2Controller.java | 150 +++++------ .../v2/dto/response/PostDetailBaseDto.java | 126 ++++----- .../dto/response/PostDetailResponseDto.java | 129 +++++----- .../response/PostV2GetPostsResponseDto.java | 7 +- .../main/post/v2/service/PostV2Service.java | 18 +- .../post/v2/service/PostV2ServiceImpl.java | 117 +++++---- .../main/user/v2/service/UserV2Service.java | 3 +- .../user/v2/service/UserV2ServiceImpl.java | 31 +-- main/src/main/resources/application-test.yml | 20 +- main/src/main/resources/schema.sql | 201 +++++++++++++++ .../main/entity/user/UserRepositoryTest.java | 19 +- .../v2/repository/ApplyRepositoryTest.java | 8 +- .../v2/repository/PostRepositoryTest.java | 239 +++++++++--------- .../sql/apply-repository-test-data.sql | 26 -- .../test/resources/sql/delete-all-data.sql | 2 +- .../sql/post-repository-test-data.sql | 26 -- 26 files changed, 854 insertions(+), 634 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java create mode 100644 main/src/main/resources/schema.sql diff --git a/main/build.gradle b/main/build.gradle index ed190c18..aa80ef21 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -36,6 +36,7 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation group: 'org.postgresql', name: 'postgresql', version: '42.6.0' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.postgresql:postgresql:42.3.0' // jsonb 타입 핸들링 위함 implementation 'io.hypersistence:hypersistence-utils-hibernate-62:3.6.0' diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/CrewDatabaseConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/CrewDatabaseConfig.java index 27bfa98b..fbfb0886 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/CrewDatabaseConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/CrewDatabaseConfig.java @@ -1,5 +1,6 @@ package org.sopt.makers.crew.main.common.config; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -7,19 +8,25 @@ import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.CommandLineRunner; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; +import jakarta.persistence.EntityManagerFactory; + @Configuration @EnableTransactionManagement @EnableJpaRepositories( @@ -27,7 +34,7 @@ entityManagerFactoryRef = "primaryEntityManagerFactory", // EntityManager의 이름 transactionManagerRef = "primaryTransactionManager" // 트랜잭션 매니저의 이름 ) -@Profile({"local", "dev", "prod"}) +@Profile({"local", "dev", "prod", "test"}) public class CrewDatabaseConfig { @Bean @@ -52,10 +59,13 @@ public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory() { Map properties = new HashMap<>(); properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"); - properties.put("hibernate.hbm2ddl.auto", "validate"); properties.put("hibernate.format_sql", true); properties.put("hibernate.physical_naming_strategy", "org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy"); - properties.put("hibernate.globally_quoted_identifiers", true); + + String[] activeProfiles = {"local", "dev", "prod"}; + if (Arrays.stream(activeProfiles).anyMatch(profile -> profile.equals(System.getProperty("spring.profiles.active")))) { + properties.put("hibernate.hbm2ddl.auto", "validate"); + } em.setJpaPropertyMap(properties); return em; @@ -68,4 +78,27 @@ public PlatformTransactionManager primaryTransactionManager( ) { return new JpaTransactionManager(Objects.requireNonNull(localContainerEntityManagerFactoryBean.getObject())); } + + private final ResourceLoader resourceLoader; + + public CrewDatabaseConfig(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + @Bean + @Profile("test") + public CommandLineRunner init(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) { + return args -> executeSchemaSql(); + } + + private void executeSchemaSql() { + Resource resource = resourceLoader.getResource("classpath:schema.sql"); + ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(); + databasePopulator.addScript(resource); + try { + databasePopulator.populate(primaryDatasourceProperties().getConnection()); + } catch (Exception e) { + System.out.println(e); + } + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/JpaAuditingConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/JpaAuditingConfig.java index ef4a1927..b4b92297 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/JpaAuditingConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/JpaAuditingConfig.java @@ -1,21 +1,34 @@ package org.sopt.makers.crew.main.common.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + import com.querydsl.jpa.JPQLTemplates; import com.querydsl.jpa.impl.JPAQueryFactory; + import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @EnableJpaAuditing @Configuration public class JpaAuditingConfig { - @PersistenceContext - private EntityManager entityManager; - @Bean - public JPAQueryFactory queryFactory() { - return new JPAQueryFactory(JPQLTemplates.DEFAULT, entityManager); - } + @PersistenceContext(unitName = "primaryEntityManagerFactory") + private EntityManager primaryEntityManager; + + @PersistenceContext(unitName = "secondEntityManagerFactory") + private EntityManager playgroundEntityManager; + + @Bean(name = "primaryQueryFactory") + @Primary + public JPAQueryFactory primaryQueryFactory() { + return new JPAQueryFactory(JPQLTemplates.DEFAULT, primaryEntityManager); + } + + @Bean(name = "playgroundQueryFactory") + public JPAQueryFactory playgroundQueryFactory() { + return new JPAQueryFactory(JPQLTemplates.DEFAULT, playgroundEntityManager); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/PlaygroundDataSourceConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/PlaygroundDataSourceConfig.java index 5cde2b6c..5c4c255f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/PlaygroundDataSourceConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/PlaygroundDataSourceConfig.java @@ -1,5 +1,6 @@ package org.sopt.makers.crew.main.common.config; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -26,7 +27,7 @@ entityManagerFactoryRef = "secondEntityManagerFactory", transactionManagerRef = "secondTransactionManager" ) -@Profile({"local", "dev", "prod"}) +@Profile({"local", "dev", "prod", "test"}) public class PlaygroundDataSourceConfig { @Bean @ConfigurationProperties("spring.playground-datasource") @@ -48,10 +49,14 @@ public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory() { Map properties = new HashMap<>(); properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"); - properties.put("hibernate.hbm2ddl.auto", "validate"); properties.put("hibernate.format_sql", true); properties.put("hibernate.physical_naming_strategy", "org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy"); - properties.put("hibernate.globally_quoted_identifiers", true); + + String[] activeProfiles = {"local", "dev", "prod"}; + if (Arrays.stream(activeProfiles).anyMatch(profile -> profile.equals(System.getProperty("spring.profiles.active")))) { + properties.put("hibernate.hbm2ddl.auto", "validate"); + } + em.setJpaPropertyMap(properties); return em; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java index df3f5db2..cfb5d811 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java @@ -7,7 +7,7 @@ import org.springframework.data.domain.Pageable; public interface PostSearchRepository { - Page findPostList(PostGetPostsCommand queryCommand, Pageable pageable, Integer userId); + Page findPostList(PostGetPostsCommand queryCommand, Pageable pageable, Integer userId); - PostDetailBaseDto findPost(Integer userId, Integer postId); + PostDetailBaseDto findPost(Integer userId, Integer postId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java index e6423581..63579a49 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java @@ -1,18 +1,11 @@ package org.sopt.makers.crew.main.entity.post; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_POST; -import static org.sopt.makers.crew.main.entity.comment.QComment.comment; -import static org.sopt.makers.crew.main.entity.like.QLike.like; -import static org.sopt.makers.crew.main.entity.meeting.QMeeting.meeting; -import static org.sopt.makers.crew.main.entity.post.QPost.post; -import static org.sopt.makers.crew.main.entity.user.QUser.user; - -import com.querydsl.core.group.GroupBy; -import com.querydsl.core.types.ExpressionUtils; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.jpa.JPAExpressions; -import com.querydsl.jpa.impl.JPAQuery; -import com.querydsl.jpa.impl.JPAQueryFactory; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; +import static org.sopt.makers.crew.main.entity.comment.QComment.*; +import static org.sopt.makers.crew.main.entity.like.QLike.*; +import static org.sopt.makers.crew.main.entity.meeting.QMeeting.*; +import static org.sopt.makers.crew.main.entity.post.QPost.*; +import static org.sopt.makers.crew.main.entity.user.QUser.*; import java.util.ArrayList; import java.util.Collections; @@ -20,8 +13,6 @@ import java.util.Map; import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; - import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.response.CommenterThumbnails; @@ -36,6 +27,15 @@ import org.springframework.data.support.PageableExecutionUtils; import org.springframework.stereotype.Repository; +import com.querydsl.core.group.GroupBy; +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + @Repository @RequiredArgsConstructor public class PostSearchRepositoryImpl implements PostSearchRepository { @@ -45,31 +45,24 @@ public class PostSearchRepositoryImpl implements PostSearchRepository { public Page findPostList(PostGetPostsCommand queryCommand, Pageable pageable, Integer userId) { Integer meetingId = queryCommand.getMeetingId().orElse(null); - List content = getContentList(pageable, meetingId, userId); + JPAQuery countQuery = getCount(meetingId); - return PageableExecutionUtils.getPage(content, - PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()), countQuery::fetchFirst); + return PageableExecutionUtils.getPage(content, PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()), + countQuery::fetchFirst); } @Override public PostDetailBaseDto findPost(Integer userId, Integer postId) { - PostDetailBaseDto postDetail = queryFactory - .select(new QPostDetailBaseDto( - post.id, post.title, post.contents, post.createdDate, post.images, - new QPostWriterInfoDto(post.user.id, post.user.orgId, post.user.name, post.user.profileImage), - post.likeCount, - ExpressionUtils.as( - JPAExpressions.selectFrom(like) - .where(like.postId.eq(post.id).and(like.userId.eq(userId))) - .exists() - , "isLiked" - ), - post.viewCount, post.commentCount, - new QPostMeetingDto(post.meeting.id, post.meeting.title, post.meeting.category, post.meeting.imageURL, - post.meeting.desc) - )) + PostDetailBaseDto postDetail = queryFactory.select( + new QPostDetailBaseDto(post.id, post.title, post.contents, post.createdDate, post.images, + new QPostWriterInfoDto(post.user.id, post.user.orgId, post.user.name, post.user.profileImage), + post.likeCount, ExpressionUtils.as( + JPAExpressions.selectFrom(like).where(like.postId.eq(post.id).and(like.userId.eq(userId))).exists(), + "isLiked"), post.viewCount, post.commentCount, + new QPostMeetingDto(post.meeting.id, post.meeting.title, post.meeting.category, post.meeting.imageURL, + post.meeting.desc))) .from(post) .innerJoin(post.meeting, meeting) .innerJoin(post.user, user) @@ -83,25 +76,15 @@ public PostDetailBaseDto findPost(Integer userId, Integer postId) { return postDetail; } - private List getContentList(Pageable pageable, Integer meetingId, - Integer userId) { - List responseDtos = new ArrayList<>(); - - List postDetailList = queryFactory - .select(new QPostDetailBaseDto( - post.id, post.title, post.contents, post.createdDate, post.images, - new QPostWriterInfoDto(post.user.id, post.user.orgId, post.user.name, post.user.profileImage), - post.likeCount, - ExpressionUtils.as( - JPAExpressions.selectFrom(like) - .where(like.postId.eq(post.id).and(like.userId.eq(userId))) - .exists() - , "isLiked" - ), - post.viewCount, post.commentCount, - new QPostMeetingDto(post.meeting.id, post.meeting.title, post.meeting.category, post.meeting.imageURL, - post.meeting.desc) - )) + private List getContentList(Pageable pageable, Integer meetingId, Integer userId) { + List postDetails = queryFactory.select( + new QPostDetailBaseDto(post.id, post.title, post.contents, post.createdDate, post.images, + new QPostWriterInfoDto(post.user.id, post.user.orgId, post.user.name, post.user.profileImage), + post.likeCount, ExpressionUtils.as( + JPAExpressions.selectFrom(like).where(like.postId.eq(post.id).and(like.userId.eq(userId))).exists(), + "isLiked"), post.viewCount, post.commentCount, + new QPostMeetingDto(post.meeting.id, post.meeting.title, post.meeting.category, post.meeting.imageURL, + post.meeting.desc))) .from(post) .innerJoin(post.user, user) .innerJoin(post.meeting, meeting) @@ -112,13 +95,11 @@ private List getContentList(Pageable pageable, Integer me .fetch(); // 모든 게시글 ID를 추출 - List postIds = postDetailList.stream() - .map(PostDetailBaseDto::getId) - .collect(Collectors.toList()); + List postIds = postDetails.stream().map(PostDetailBaseDto::getId).collect(Collectors.toList()); - // 게시글 ID 리스트를 사용하여 한 번에 모든 댓글 작성자의 프로필 이미지를 조회 - Map> commenterThumbnailsMap = queryFactory - .select(comment.post.id, comment.user.profileImage) + // 게시글 ID 리스트를 사용하여 모든 댓글 작성자의 프로필 이미지를 조회 + Map> commenterThumbnailsMap = queryFactory.select(comment.post.id, + comment.user.profileImage) .from(comment) .where(comment.post.id.in(postIds)) .groupBy(comment.post.id, comment.user.id, comment.user.profileImage) @@ -127,25 +108,22 @@ private List getContentList(Pageable pageable, Integer me .transform(GroupBy.groupBy(comment.post.id).as(GroupBy.list(comment.user.profileImage))); // 각 게시글별로 댓글 작성자의 프로필 이미지 리스트를 설정 - for (PostDetailBaseDto postDetail : postDetailList) { + List responseDtos = new ArrayList<>(); + for (PostDetailBaseDto postDetail : postDetails) { CommenterThumbnails commenterThumbnails = new CommenterThumbnails( - commenterThumbnailsMap.getOrDefault(postDetail.getId(), - Collections.emptyList())); - responseDtos.add(PostDetailResponseDto.of(postDetail, commenterThumbnails)); + commenterThumbnailsMap.getOrDefault(postDetail.getId(), Collections.emptyList())); + responseDtos.add( + PostDetailResponseDto.of(postDetail, commenterThumbnails, false)); } return responseDtos; } private JPAQuery getCount(Integer meetingId) { - return queryFactory - .select(post.count()) - .from(post) - .where(meetingIdEq(meetingId)); + return queryFactory.select(post.count()).from(post).where(meetingIdEq(meetingId)); } private BooleanExpression meetingIdEq(Integer meetingId) { return meetingId == null ? null : post.meetingId.eq(meetingId); } - } diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/entity/member_block/MemberBlockRepository.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/entity/member_block/MemberBlockRepository.java index f7e9089f..628aa4c5 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/external/playground/entity/member_block/MemberBlockRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/entity/member_block/MemberBlockRepository.java @@ -1,6 +1,9 @@ package org.sopt.makers.crew.main.external.playground.entity.member_block; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; public interface MemberBlockRepository extends JpaRepository { -} + List findAllByBlockerAndIsBlockedTrue(Long orgId); +} \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java new file mode 100644 index 00000000..d125cf78 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java @@ -0,0 +1,8 @@ +package org.sopt.makers.crew.main.external.playground.service; + +import java.util.List; +import java.util.Map; + +public interface MemberBlockService { + Map getBlockedUsers(Long blockerOrgId, List userOrgIds); +} \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java new file mode 100644 index 00000000..9d600a0e --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java @@ -0,0 +1,27 @@ +package org.sopt.makers.crew.main.external.playground.service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.sopt.makers.crew.main.external.playground.entity.member_block.MemberBlock; +import org.sopt.makers.crew.main.external.playground.entity.member_block.MemberBlockRepository; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class MemberBlockServiceImpl implements MemberBlockService { + + private final MemberBlockRepository memberBlockRepository; + + @Override + public Map getBlockedUsers(Long blockerOrgId, List userOrgIds) { + List memberBlocks = memberBlockRepository.findAllByBlockerAndIsBlockedTrue(blockerOrgId); + + return memberBlocks.stream() + .filter(memberBlock -> userOrgIds.contains(memberBlock.getBlockedMember())) + .collect(Collectors.toMap(MemberBlock::getBlockedMember, memberBlock -> true)); + } +} \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java index e4465313..aa189606 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java @@ -1,15 +1,7 @@ package org.sopt.makers.crew.main.post.v2; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import java.security.Principal; + import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; @@ -27,78 +19,70 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + @Tag(name = "게시글") public interface PostV2Api { - @Operation(summary = "모임 게시글 작성") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content), - }) - ResponseEntity createPost( - @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal); + @Operation(summary = "모임 게시글 작성") + @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content),}) + ResponseEntity createPost(@Valid @RequestBody PostV2CreatePostBodyDto requestBody, + Principal principal); - @Operation(summary = "모임 게시글 목록 조회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), - }) - @Parameters({ - @Parameter(name = "page", description = "페이지, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), - @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), - @Parameter(name = "meetingId", description = "모임 id", example = "0", schema = @Schema(type = "integer", format = "int32"))}) - ResponseEntity getPosts( - @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, - Principal principal); + @Operation(summary = "모임 게시글 목록 조회") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content),}) + @Parameters({ + @Parameter(name = "page", description = "페이지, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "meetingId", description = "모임 id", example = "0", schema = @Schema(type = "integer", format = "int32"))}) + ResponseEntity getPosts( + @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, Principal principal); - @Operation(summary = "게시글에서 멘션하기") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - }) - ResponseEntity mentionUserInPost( - @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal); + @Operation(summary = "게시글에서 멘션하기") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"),}) + ResponseEntity mentionUserInPost(@Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, + Principal principal); - @Operation(summary = "모임 게시글 조회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content) - }) - ResponseEntity getPost(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 조회") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content)}) + ResponseEntity getPost(@PathVariable Integer postId, Principal principal); - @Operation(summary = "모임 게시글 개수 조회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content) - }) - ResponseEntity getPostCount(@RequestParam Integer meetingId); + @Operation(summary = "모임 게시글 개수 조회") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content)}) + ResponseEntity getPostCount(@RequestParam Integer meetingId); - @Operation(summary = "모임 게시글 삭제") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content) - }) - ResponseEntity deletePost(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 삭제") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content)}) + ResponseEntity deletePost(@PathVariable Integer postId, Principal principal); - @Operation(summary = "모임 게시글 수정") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미지는 최대 10개까지만 업로드 가능합니다.\"", content = @Content), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content) - }) - ResponseEntity updatePost(@PathVariable Integer postId, - @RequestBody PostV2UpdatePostBodyDto requestBody, - Principal principal); + @Operation(summary = "모임 게시글 수정") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미지는 최대 10개까지만 업로드 가능합니다.\"", content = @Content), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content)}) + ResponseEntity updatePost(@PathVariable Integer postId, + @RequestBody PostV2UpdatePostBodyDto requestBody, Principal principal); - @Operation(summary = "모임 게시글 신고") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "성공"), - @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미 신고한 게시글입니다.\"", content = @Content) - }) - ResponseEntity reportPost(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 신고") + @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "성공"), + @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미 신고한 게시글입니다.\"", content = @Content)}) + ResponseEntity reportPost(@PathVariable Integer postId, Principal principal); - @Operation(summary = "모임 게시글 좋아요 토글") - @ApiResponse(responseCode = "201", description = "성공") - ResponseEntity switchPostLike(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 좋아요 토글") + @ApiResponse(responseCode = "201", description = "성공") + ResponseEntity switchPostLike(@PathVariable Integer postId, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java index 81361c55..76b61f99 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java @@ -1,9 +1,7 @@ package org.sopt.makers.crew.main.post.v2; -import io.swagger.v3.oas.annotations.Parameter; -import jakarta.validation.Valid; import java.security.Principal; -import lombok.RequiredArgsConstructor; + import org.sopt.makers.crew.main.common.util.UserUtil; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; @@ -31,91 +29,93 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + @RestController @RequestMapping("/post/v2") @RequiredArgsConstructor public class PostV2Controller implements PostV2Api { - private final PostV2Service postV2Service; + private final PostV2Service postV2Service; - @Override - @PostMapping() - @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity createPost( - @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.createPost(requestBody, userId)); - } + @Override + @PostMapping() + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity createPost( + @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.createPost(requestBody, userId)); + } - @Override - @GetMapping() - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getPosts( - @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.getPosts(queryCommand, userId)); - } + @Override + @GetMapping() + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPosts( + @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.getPosts(queryCommand, userId)); + } - @Override - @PostMapping("/mention") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity mentionUserInPost( - @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - postV2Service.mentionUserInPost(requestBody, userId); - return ResponseEntity.status(HttpStatus.OK).build(); - } + @Override + @PostMapping("/mention") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity mentionUserInPost(@Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + postV2Service.mentionUserInPost(requestBody, userId); + return ResponseEntity.status(HttpStatus.OK).build(); + } - @Override - @GetMapping("/{postId}") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getPost(@PathVariable Integer postId, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.getPost(userId, postId)); - } + @Override + @GetMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.getPost(userId, postId)); + } - @Override - @GetMapping("/count") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getPostCount(@RequestParam Integer meetingId) { - return ResponseEntity.ok(postV2Service.getPostCount(meetingId)); - } + @Override + @GetMapping("/count") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPostCount(@RequestParam Integer meetingId) { + return ResponseEntity.ok(postV2Service.getPostCount(meetingId)); + } - @Override - @DeleteMapping("/{postId}") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity deletePost(@PathVariable Integer postId, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - postV2Service.deletePost(postId, userId); - return ResponseEntity.ok().build(); - } + @Override + @DeleteMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity deletePost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + postV2Service.deletePost(postId, userId); + return ResponseEntity.ok().build(); + } - @Override - @PutMapping("/{postId}") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity updatePost(@PathVariable Integer postId, - @RequestBody PostV2UpdatePostBodyDto requestBody, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.updatePost(postId, requestBody, userId)); - } + @Override + @PutMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity updatePost(@PathVariable Integer postId, + @RequestBody PostV2UpdatePostBodyDto requestBody, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.updatePost(postId, requestBody, userId)); + } - @Override - @PostMapping("/{postId}/report") - @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity reportPost(@PathVariable Integer postId, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.reportPost(postId, userId)); - } + @Override + @PostMapping("/{postId}/report") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity reportPost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.reportPost(postId, userId)); + } - @Override - @PostMapping("/{postId}/like") - @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity switchPostLike(@PathVariable Integer postId, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.switchPostLike(postId, userId)); - } + @Override + @PostMapping("/{postId}/like") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity switchPostLike(@PathVariable Integer postId, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.switchPostLike(postId, userId)); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java index 60e3e3b9..5fb3bef5 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java @@ -1,75 +1,75 @@ package org.sopt.makers.crew.main.post.v2.dto.response; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.querydsl.core.annotations.QueryProjection; import java.time.LocalDateTime; +import com.querydsl.core.annotations.QueryProjection; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter -@Schema(name = "PostDetailBaseDto", description = "게시글 객체 Dto") +@Schema(name = "PostDetailBaseDto", description = "게시글의 기본 정보를 담고 있는 Dto") public class PostDetailBaseDto { - @Schema(description = "게시글 id", example = "1") - @NotNull - private final Integer id; - - @Schema(description = "게시글 제목", example = "게시글 제목입니다.") - @NotNull - private final String title; - - @Schema(description = "게시글 내용", example = "게시글 내용입니다.") - @NotNull - private final String contents; - - @Schema(description = "게시글 생성일자", example = "2024-07-31T15:30:00") - @NotNull - private final LocalDateTime createdDate; - - @Schema(description = "게시글 이미지", example = "[\"url1\", \"url2\"]") - @NotNull - private final String[] images; - - @Schema(description = "게시글 작성자 객체", example = "") - @NotNull - private final PostWriterInfoDto user; - - @Schema(description = "게시글 좋아요 갯수", example = "20") - private final int likeCount; - - //* 본인이 좋아요를 눌렀는지 여부 - @Schema(description = "게시글 좋아요 여부", example = "true") - @NotNull - private final Boolean isLiked; - - @Schema(description = "게시글 조회수", example = "30") - @NotNull - private final int viewCount; - - @Schema(description = "게시글 댓글 수", example = "5") - @NotNull - private final int commentCount; - - @Schema(description = "게시글에 대한 모임", example = "") - @NotNull - private final PostMeetingDto meeting; - - @QueryProjection - public PostDetailBaseDto(Integer id, String title, String contents, LocalDateTime createdDate, String[] images, - PostWriterInfoDto user, int likeCount, boolean isLiked, int viewCount, int commentCount, - PostMeetingDto meeting) { - this.id = id; - this.title = title; - this.contents = contents; - this.createdDate = createdDate; - this.images = images; - this.user = user; - this.likeCount = likeCount; - this.isLiked = isLiked; - this.viewCount = viewCount; - this.commentCount = commentCount; - this.meeting = meeting; - } + @Schema(description = "게시글 id", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "게시글 제목", example = "게시글 제목입니다.") + @NotNull + private final String title; + + @Schema(description = "게시글 내용", example = "게시글 내용입니다.") + @NotNull + private final String contents; + + @Schema(description = "게시글 생성일자", example = "2024-07-31T15:30:00") + @NotNull + private final LocalDateTime createdDate; + + @Schema(description = "게시글 이미지", example = "[\"url1\", \"url2\"]") + @NotNull + private final String[] images; + + @Schema(description = "게시글 작성자 객체", example = "") + @NotNull + private final PostWriterInfoDto user; + + @Schema(description = "게시글 좋아요 갯수", example = "20") + private final int likeCount; + + //* 본인이 좋아요를 눌렀는지 여부 + @Schema(description = "게시글 좋아요 여부", example = "true") + @NotNull + private final Boolean isLiked; + + @Schema(description = "게시글 조회수", example = "30") + @NotNull + private final int viewCount; + + @Schema(description = "게시글 댓글 수", example = "5") + @NotNull + private final int commentCount; + + @Schema(description = "게시글에 대한 모임", example = "") + @NotNull + private final PostMeetingDto meeting; + + @QueryProjection + public PostDetailBaseDto(Integer id, String title, String contents, LocalDateTime createdDate, String[] images, + PostWriterInfoDto user, int likeCount, boolean isLiked, int viewCount, int commentCount, + PostMeetingDto meeting) { + this.id = id; + this.title = title; + this.contents = contents; + this.createdDate = createdDate; + this.images = images; + this.user = user; + this.likeCount = likeCount; + this.isLiked = isLiked; + this.viewCount = viewCount; + this.commentCount = commentCount; + this.meeting = meeting; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java index a05e19e4..fd35d954 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java @@ -10,73 +10,66 @@ @Getter @AllArgsConstructor(staticName = "of") -@Schema(name = "PostDetailResponseDto", description = "게시글 객체 Dto") +@Schema(name = "PostDetailResponseDto", description = "게시글의 기본 정보 + 댓글 썸네일 이미지 리스트 정보을 담고 있는 DTO") public class PostDetailResponseDto { - @Schema(description = "게시글 id", example = "1") - @NotNull - private final Integer id; - - @Schema(description = "게시글 제목", example = "게시글 제목입니다.") - @NotNull - private final String title; - - @Schema(description = "게시글 내용", example = "게시글 내용입니다.") - @NotNull - private final String contents; - - @Schema(description = "게시글 생성 일자", example = "2024-05-31T15:30:00") - @NotNull - private final LocalDateTime createdDate; - - @Schema(description = "게시글 이미지 목록", example = "[\"url1\", \"url2\"]") - @NotNull - private final String[] images; - - @Schema(description = "게시글 생성 유저 객체", example = "") - @NotNull - private final PostWriterInfoDto user; - - @Schema(description = "게시글 좋아요 수", example = "20") - @NotNull - private final int likeCount; - - @Schema(description = "게시글 좋아요 여부", example = "true") - @NotNull - private final Boolean isLiked; - - @Schema(description = "게시글 조회수", example = "200") - @NotNull - private final int viewCount; - - @Schema(description = "게시글 댓글 수", example = "30") - @NotNull - private final int commentCount; - - @Schema(description = "게시글에 해당하는 모임", example = "") - @NotNull - private final PostMeetingDto meeting; - - @Schema(description = "댓글 작성자 썸네일 목록", example = "[\"url1\", \"url2\"]") - @NotNull - private final List commenterThumbnails; - - public static PostDetailResponseDto of(PostDetailBaseDto postDetail, - CommenterThumbnails postTopCommenterThumbnails) { - List thumbnails = postTopCommenterThumbnails.getCommenterThumbnails(); - return PostDetailResponseDto.of( - postDetail.getId(), - postDetail.getTitle(), - postDetail.getContents(), - postDetail.getCreatedDate(), - postDetail.getImages(), - postDetail.getUser(), - postDetail.getLikeCount(), - postDetail.getIsLiked(), - postDetail.getViewCount(), - postDetail.getCommentCount(), - postDetail.getMeeting(), - thumbnails - ); - } -} + @Schema(description = "게시글 id", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "게시글 제목", example = "게시글 제목입니다.") + @NotNull + private final String title; + + @Schema(description = "게시글 내용", example = "게시글 내용입니다.") + @NotNull + private final String contents; + + @Schema(description = "게시글 생성 일자", example = "2024-05-31T15:30:00") + @NotNull + private final LocalDateTime createdDate; + + @Schema(description = "게시글 이미지 목록", example = "[\"url1\", \"url2\"]") + @NotNull + private final String[] images; + + @Schema(description = "게시글 생성 유저 객체", example = "") + @NotNull + private final PostWriterInfoDto user; + + @Schema(description = "게시글 좋아요 수", example = "20") + @NotNull + private final int likeCount; + + @Schema(description = "게시글 좋아요 여부", example = "true") + @NotNull + private final Boolean isLiked; + + @Schema(description = "게시글 조회수", example = "200") + @NotNull + private final int viewCount; + + @Schema(description = "게시글 댓글 수", example = "30") + @NotNull + private final int commentCount; + + @Schema(description = "게시글에 해당하는 모임", example = "") + @NotNull + private final PostMeetingDto meeting; + + @Schema(description = "댓글 작성자 썸네일 목록", example = "[\"url1\", \"url2\"]") + @NotNull + private final List commenterThumbnails; + + @Schema(description = "차단된 유저의 게시물인지 여부", example = "false") + @NotNull + private final boolean isBlockedPost; + + public static PostDetailResponseDto of(PostDetailBaseDto postDetail, + CommenterThumbnails postTopCommenterThumbnails, boolean isBlockedPost) { + return PostDetailResponseDto.of(postDetail.getId(), postDetail.getTitle(), postDetail.getContents(), + postDetail.getCreatedDate(), postDetail.getImages(), postDetail.getUser(), postDetail.getLikeCount(), + postDetail.getIsLiked(), postDetail.getViewCount(), postDetail.getCommentCount(), postDetail.getMeeting(), + postTopCommenterThumbnails.getCommenterThumbnails(), isBlockedPost); + } +} \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java index dd38fa9b..6f47e304 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java @@ -2,22 +2,23 @@ import java.util.List; +import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; -import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; - @Getter @AllArgsConstructor(staticName = "of") @Schema(name = "PostV2GetPostsResponseDto", description = "게시글 조회 응답 Dto") public class PostV2GetPostsResponseDto { - @Schema(description = "게시글 객체", example = "") + @Schema(description = "게시글의 기본 정보 + 댓글 썸네일 이미지 리스트 정보 + 차단된 유저의 게시물인지 아닌지 정보를 담고 있는 DTO") @NotNull private final List posts; + @Schema(description = "페이지 메타 정보") @NotNull private final PageMetaDto meta; diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java index 4e7da2c4..3b073371 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java @@ -14,21 +14,21 @@ public interface PostV2Service { - PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId); + PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId); - PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId); + PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId); - PostDetailBaseDto getPost(Integer userId, Integer postId); + PostDetailBaseDto getPost(Integer userId, Integer postId); - void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId); + void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId); - PostV2GetPostCountResponseDto getPostCount(Integer meetingId); + PostV2GetPostCountResponseDto getPostCount(Integer meetingId); - void deletePost(Integer postId, Integer userId); + void deletePost(Integer postId, Integer userId); - PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId); + PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId); - PostV2ReportResponseDto reportPost(Integer postId, Integer userId); + PostV2ReportResponseDto reportPost(Integer postId, Integer userId); - PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer userId); + PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java index 41fad4b2..ef18de60 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java @@ -1,16 +1,12 @@ package org.sopt.makers.crew.main.post.v2.service; -import static java.util.stream.Collectors.toList; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.ALREADY_REPORTED_POST; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FORBIDDEN_EXCEPTION; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.MAX_IMAGE_UPLOAD_EXCEEDED; -import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE; -import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_PUSH_NOTIFICATION_TITLE; -import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.PUSH_NOTIFICATION_CATEGORY; +import static java.util.stream.Collectors.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; +import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.*; import java.util.List; - -import lombok.RequiredArgsConstructor; +import java.util.Map; +import java.util.stream.Collectors; import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.common.exception.ForbiddenException; @@ -33,6 +29,7 @@ import org.sopt.makers.crew.main.entity.report.ReportRepository; import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.entity.user.UserRepository; +import org.sopt.makers.crew.main.external.playground.service.MemberBlockService; import org.sopt.makers.crew.main.internal.notification.PushNotificationService; import org.sopt.makers.crew.main.internal.notification.dto.PushNotificationRequestDto; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; @@ -47,13 +44,15 @@ import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; +import org.sopt.makers.crew.main.user.v2.service.UserV2Service; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import lombok.RequiredArgsConstructor; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -68,6 +67,8 @@ public class PostV2ServiceImpl implements PostV2Service { private final ReportRepository reportRepository; private final PushNotificationService pushNotificationService; + private final MemberBlockService memberBlockService; + private final UserV2Service userV2Service; @Value("${push-notification.web-url}") private String pushWebUrl; @@ -82,16 +83,14 @@ public class PostV2ServiceImpl implements PostV2Service { */ @Override @Transactional - public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, - Integer userId) { + public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId) { Meeting meeting = meetingRepository.findByIdOrThrow(requestBody.getMeetingId()); User user = userRepository.findByIdOrThrow(userId); List applies = applyRepository.findAllByMeetingId(meeting.getId()); boolean isInMeeting = applies.stream() - .anyMatch(apply -> apply.getUserId().equals(userId) - && apply.getStatus().equals(EnApplyStatus.APPROVE)); + .anyMatch(apply -> apply.getUserId().equals(userId) && apply.getStatus().equals(EnApplyStatus.APPROVE)); boolean isMeetingCreator = meeting.getUserId().equals(userId); @@ -109,27 +108,32 @@ public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBod Post savedPost = postRepository.save(post); - List userIdList = applyRepository.findAllByMeetingIdAndStatus(meeting.getId(), - EnApplyStatus.APPROVE) + List userIdList = applyRepository.findAllByMeetingIdAndStatus(meeting.getId(), EnApplyStatus.APPROVE) .stream() .map(apply -> String.valueOf(apply.getUser().getOrgId())) .collect(toList()); String[] userIds = userIdList.toArray(new String[0]); - String pushNotificationContent = String.format("[%s의 새 글] : \"%s\"", - user.getName(), post.getTitle()); + String pushNotificationContent = String.format("[%s의 새 글] : \"%s\"", user.getName(), post.getTitle()); String pushNotificationWeblink = pushWebUrl + "/detail?id=" + meeting.getId(); PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of(userIds, - NEW_POST_PUSH_NOTIFICATION_TITLE.getValue(), - pushNotificationContent, - PUSH_NOTIFICATION_CATEGORY.getValue(), pushNotificationWeblink); + NEW_POST_PUSH_NOTIFICATION_TITLE.getValue(), pushNotificationContent, PUSH_NOTIFICATION_CATEGORY.getValue(), + pushNotificationWeblink); pushNotificationService.sendPushNotification(pushRequestDto); return PostV2CreatePostResponseDto.of(savedPost.getId()); } + /** + * 모일 게시글 리스트 페이지네이션 조회 (12개) + * + * @param queryCommand 게시글 조회를 위한 쿼리 명령 객체 + * @param userId 게시글을 조회하는 사용자 id + * @return 게시글 정보(게시글 객체 + 댓글 단 사람의 썸네일 + 차단된 유저의 게시물 여부)와 페이지 메타 정보를 포함한 응답 DTO + * @apiNote 사용자가 차단한 유저의 게시물은 해당 게시물에 대한 차단 여부를 함께 반환 + */ @Override @Transactional(readOnly = true) public PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId) { @@ -139,10 +143,24 @@ public PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Inte PageOptionsDto pageOptionsDto = new PageOptionsDto(meetingPostListDtos.getPageable().getPageNumber() + 1, meetingPostListDtos.getPageable().getPageSize()); - PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, - (int)meetingPostListDtos.getTotalElements()); + PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, (int)meetingPostListDtos.getTotalElements()); + + List userOrgIds = meetingPostListDtos.getContent() + .stream() + .map(postDetail -> postDetail.getUser().getOrgId().longValue()) + .collect(Collectors.toList()); - return PostV2GetPostsResponseDto.of(meetingPostListDtos.getContent(), pageMetaDto); + User user = userV2Service.getUserByUserId(userId); + Long orgId = user.getOrgId().longValue(); + + Map blockedPostMap = memberBlockService.getBlockedUsers(orgId, userOrgIds); + + List responseDtos = meetingPostListDtos.getContent() + .stream() + .map(postDetail -> toPostDetailResponseDto(postDetail, blockedPostMap)) + .collect(Collectors.toList()); + + return PostV2GetPostsResponseDto.of(responseDtos, pageMetaDto); } /** @@ -162,21 +180,14 @@ public void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Int User user = userRepository.findByIdOrThrow(userId); Post post = postRepository.findByIdOrThrow(requestBody.getPostId()); - String pushNotificationContent = String.format("[%s의 글] : \"%s\"", - user.getName(), post.getTitle()); + String pushNotificationContent = String.format("[%s의 글] : \"%s\"", user.getName(), post.getTitle()); String pushNotificationWeblink = pushWebUrl + "/post?id=" + post.getId(); - String[] userOrgIds = requestBody.getOrgIds().stream() - .map(Object::toString) - .toArray(String[]::new); + String[] userOrgIds = requestBody.getOrgIds().stream().map(Object::toString).toArray(String[]::new); - PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of( - userOrgIds, - NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE.getValue(), - pushNotificationContent, - PUSH_NOTIFICATION_CATEGORY.getValue(), - pushNotificationWeblink - ); + PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of(userOrgIds, + NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE.getValue(), pushNotificationContent, + PUSH_NOTIFICATION_CATEGORY.getValue(), pushNotificationWeblink); pushNotificationService.sendPushNotification(pushRequestDto); } @@ -235,8 +246,7 @@ public PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBo post.updatePost(requestBody.getTitle(), requestBody.getContents(), requestBody.getImages()); return PostV2UpdatePostResponseDto.of(post.getId(), post.getTitle(), post.getContents(), - String.valueOf(time.now()), - post.getImages()); + String.valueOf(time.now()), post.getImages()); } /** @@ -258,11 +268,7 @@ public PostV2ReportResponseDto reportPost(Integer postId, Integer userId) { throw new BadRequestException(ALREADY_REPORTED_POST.getErrorCode()); } - Report report = Report.builder() - .post(post) - .postId(postId) - .userId(userId) - .build(); + Report report = Report.builder().post(post).postId(postId).userId(userId).build(); Report savedReport = reportRepository.save(report); @@ -288,10 +294,7 @@ public PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer us // 취소된 좋아요 정보가 없을 경우 if (deletedLikes == 0) { - Like newLike = Like.builder() - .postId(postId) - .userId(userId) - .build(); + Like newLike = Like.builder().postId(postId).userId(userId).build(); likeRepository.save(newLike); @@ -306,4 +309,24 @@ public PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer us return PostV2SwitchPostLikeResponseDto.of(false); } + + private PostDetailResponseDto toPostDetailResponseDto(PostDetailResponseDto postDetail, + Map blockedPostMap) { + boolean isBlockedPost = blockedPostMap.getOrDefault(postDetail.getUser().getOrgId().longValue(), false); + return PostDetailResponseDto.of( + postDetail.getId(), + postDetail.getTitle(), + postDetail.getContents(), + postDetail.getCreatedDate(), + postDetail.getImages(), + postDetail.getUser(), + postDetail.getLikeCount(), + postDetail.getIsLiked(), + postDetail.getViewCount(), + postDetail.getCommentCount(), + postDetail.getMeeting(), + postDetail.getCommenterThumbnails(), + isBlockedPost + ); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java index 1b71514d..deb1fb9b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java @@ -2,6 +2,7 @@ import java.util.List; +import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAppliedMeetingByUserResponseDto; @@ -20,5 +21,5 @@ public interface UserV2Service { UserV2GetCreatedMeetingByUserResponseDto getCreatedMeetingByUser(Integer userId); - + User getUserByUserId(Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java index 6bdc648d..17e792c5 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java @@ -5,8 +5,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import lombok.RequiredArgsConstructor; - import org.sopt.makers.crew.main.common.exception.BaseException; import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.apply.Applies; @@ -28,6 +26,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import lombok.RequiredArgsConstructor; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -45,19 +45,12 @@ public List getAllMeetingByUser(Integer use List myMeetings = meetingRepository.findAllByUserId(user.getId()); - List userJoinedList = Stream.concat( - myMeetings.stream(), + List userJoinedList = Stream.concat(myMeetings.stream(), applyRepository.findAllByUserIdAndStatus(userId, EnApplyStatus.APPROVE) .stream() - .map(apply -> apply.getMeeting()) - ) - .map(meeting -> UserV2GetAllMeetingByUserMeetingDto.of( - meeting.getId(), - meeting.getTitle(), - meeting.getDesc(), - meeting.getImageURL().get(0).getUrl(), - meeting.getCategory().getValue() - )) + .map(apply -> apply.getMeeting())) + .map(meeting -> UserV2GetAllMeetingByUserMeetingDto.of(meeting.getId(), meeting.getTitle(), + meeting.getDesc(), meeting.getImageURL().get(0).getUrl(), meeting.getCategory().getValue())) .sorted(Comparator.comparing(UserV2GetAllMeetingByUserMeetingDto::getId).reversed()) .collect(Collectors.toList()); @@ -110,12 +103,16 @@ public UserV2GetAppliedMeetingByUserResponseDto getAppliedMeetingByUser(Integer Applies allApplies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); List appliedMeetingByUserDtos = myApplies.stream() - .map(apply -> ApplyV2GetAppliedMeetingByUserResponseDto.of( - apply.getId(), apply.getStatus().getValue(), + .map(apply -> ApplyV2GetAppliedMeetingByUserResponseDto.of(apply.getId(), apply.getStatus().getValue(), MeetingV2GetCreatedMeetingByUserResponseDto.of(apply.getMeeting(), apply.getMeeting().getUser(), - allApplies.getApprovedCount(apply.getMeetingId()), time.now()) - )).toList(); + allApplies.getApprovedCount(apply.getMeetingId()), time.now()))) + .toList(); return UserV2GetAppliedMeetingByUserResponseDto.of(appliedMeetingByUserDtos); } + + @Override + public User getUserByUserId(Integer userId) { + return userRepository.findByIdOrThrow(userId); + } } diff --git a/main/src/main/resources/application-test.yml b/main/src/main/resources/application-test.yml index a0e60eb0..ad43358a 100644 --- a/main/src/main/resources/application-test.yml +++ b/main/src/main/resources/application-test.yml @@ -1,5 +1,3 @@ - - server: port: 4000 @@ -10,15 +8,16 @@ spring: import: application-secret.properties datasource: driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - url: jdbc:tc:postgresql:///test-database - # username: root - # password: + jdbc-url: jdbc:tc:postgresql:///crew + + playground-datasource: + jdbc-url: jdbc:tc:postgresql:///playground jpa: open-in-view: false hibernate: naming: physical-strategy: org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy - ddl-auto: create-drop + ddl-auto: none properties: hibernate: show_sql: true @@ -64,7 +63,7 @@ push-notification: push-server-url: ${DEV_PUSH_SERVER_URL} notice: - secret-key : ${NOTICE_SECRET_KEY} + secret-key: ${NOTICE_SECRET_KEY} playground: server: @@ -94,4 +93,9 @@ management: prometheus: metrics: export: - enabled: true \ No newline at end of file + enabled: true + +logging: + level: + org.hibernate.SQL: DEBUG # Hibernate의 SQL 쿼리 로그 레벨 설정 + org.hibernate.type: TRACE # SQL 파라미터 바인딩을 보기 위한 타입 정보 로그 출력 diff --git a/main/src/main/resources/schema.sql b/main/src/main/resources/schema.sql new file mode 100644 index 00000000..9b2d7880 --- /dev/null +++ b/main/src/main/resources/schema.sql @@ -0,0 +1,201 @@ +DROP TYPE IF EXISTS meeting_joinableparts_enum; + +create type meeting_joinableparts_enum as enum ('PM', 'DESIGN', 'IOS', 'ANDROID', 'SERVER', 'WEB'); + +create table if not exists "user" +( + id serial + constraint "PK_cace4a159ff9f2512dd42373760" + primary key + constraint "UQ_cace4a159ff9f2512dd42373760" + unique, + name varchar not null, + "orgId" integer not null, + "profileImage" varchar, + activities jsonb, + phone varchar +); + +-- alter table "user" +-- owner to songmingyu; + +create table if not exists meeting +( + id serial + constraint "PK_dccaf9e4c0e39067d82ccc7bb83" + primary key + constraint "UQ_dccaf9e4c0e39067d82ccc7bb83" + unique, + "userId" integer not null + constraint "FK_854982a74818bb6307419e0e6b8" + references "user" + on delete cascade, + title varchar not null, + category varchar not null, + "imageURL" jsonb not null, + "startDate" timestamp not null, + "endDate" timestamp not null, + capacity integer not null, + "desc" varchar not null, + "processDesc" varchar not null, + "mStartDate" timestamp not null, + "mEndDate" timestamp not null, + "leaderDesc" varchar not null, + "targetDesc" varchar not null, + note varchar, + "isMentorNeeded" boolean not null, + "canJoinOnlyActiveGeneration" boolean not null, + "targetActiveGeneration" integer, + "joinableParts" meeting_joinableparts_enum[], + "createdGeneration" integer default 32 +); + +create table if not exists apply +( + id serial + constraint "PK_c61ed680472aa0f58499175d902" + primary key, + type integer default 0 not null, + "meetingId" integer not null + constraint "FK_b130d23f4642d1ef51c6e54d257" + references meeting + on delete cascade, + "userId" integer not null + constraint "FK_359c8244808809db5ee96ed066e" + references "user" + on delete cascade, + content varchar not null, + "appliedDate" timestamp not null, + status integer default 0 not null +); + +-- alter table apply +-- owner to songmingyu; + +create index if not exists "meetingId_index" + on apply ("meetingId"); + +create index if not exists "userId_index" + on apply ("userId"); + +create table if not exists notice +( + id serial + constraint "PK_705062b14410ff1a04998f86d72" + primary key + constraint "UQ_705062b14410ff1a04998f86d72" + unique, + title varchar not null, + "subTitle" varchar not null, + contents varchar not null, + "createdDate" timestamp not null, + "exposeStartDate" timestamp not null, + "exposeEndDate" timestamp not null +); + +create table if not exists post +( + id serial + constraint "PK_be5fda3aac270b134ff9c21cdee" + primary key + constraint "UQ_be5fda3aac270b134ff9c21cdee" + unique, + contents varchar not null, + "createdDate" timestamp not null, + "updatedDate" timestamp not null, + "likeCount" integer default 0 not null, + "userId" integer not null + constraint "FK_5c1cf55c308037b5aca1038a131" + references "user", + "meetingId" integer not null + constraint "FK_85e980cf9166f5337c0b2b76bc0" + references meeting, + title varchar not null, + "viewCount" integer default 0 not null, + images text[], + "commentCount" integer default 0 not null +); + +-- alter table post +-- owner to songmingyu; + +create table if not exists comment +( + id serial + constraint "PK_0b0e4bbc8415ec426f87f3a88e2" + primary key + constraint "UQ_0b0e4bbc8415ec426f87f3a88e2" + unique, + contents varchar not null, + depth integer default 0 not null, + "order" integer default 0 not null, + "createdDate" timestamp not null, + "updatedDate" timestamp not null, + "likeCount" integer default 0 not null, + "userId" integer + constraint "FK_c0354a9a009d3bb45a08655ce3b" + references "user", + "postId" integer not null + constraint "FK_94a85bb16d24033a2afdd5df060" + references post + on delete cascade, + "parentId" integer +); + +create table if not exists "like" +( + id serial + constraint "PK_eff3e46d24d416b52a7e0ae4159" + primary key + constraint "UQ_eff3e46d24d416b52a7e0ae4159" + unique, + "createdDate" timestamp not null, + "userId" integer not null + constraint "FK_e8fb739f08d47955a39850fac23" + references "user", + "postId" integer + constraint "FK_3acf7c55c319c4000e8056c1279" + references post + on delete cascade, + "commentId" integer + constraint "FK_d86e0a3eeecc21faa0da415a18a" + references comment + on delete cascade +); + +create table if not exists report +( + id serial + constraint "PK_99e4d0bea58cba73c57f935a546" + primary key + constraint "UQ_99e4d0bea58cba73c57f935a546" + unique, + "createdDate" timestamp not null, + "commentId" integer + constraint "FK_97372830f2390803a3e2df4a46e" + references comment, + "userId" integer not null + constraint "FK_e347c56b008c2057c9887e230aa" + references "user" + on delete cascade, + "postId" integer + constraint "FK_4b6fe2df37305bc075a4a16d3ea" + references post + on delete cascade +); + +create table if not exists advertisement +( + id serial + primary key, + "advertisementLink" varchar(255), + "advertisementEndDate" timestamp(6), + "advertisementStartDate" timestamp(6), + priority bigint, + "advertisementCategory" varchar(255) + constraint "advertisement_advertisementCategory_check" + check (("advertisementCategory")::text = ANY +((ARRAY ['POST'::character varying, 'MEETING'::character varying])::text[])), + "advertisementDesktopImageUrl" varchar(255), + "advertisementMobileImageUrl" varchar(255) + ); \ No newline at end of file diff --git a/main/src/test/java/org/sopt/makers/crew/main/entity/user/UserRepositoryTest.java b/main/src/test/java/org/sopt/makers/crew/main/entity/user/UserRepositoryTest.java index 21d5a8b6..8251eb72 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/entity/user/UserRepositoryTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/entity/user/UserRepositoryTest.java @@ -2,17 +2,19 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import org.sopt.makers.crew.main.common.config.TestConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.context.annotation.Import; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlGroup; +import org.testcontainers.junit.jupiter.Testcontainers; -@DataJpaTest +@SpringBootTest +@Testcontainers @ActiveProfiles("test") -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Import(TestConfig.class) +@SqlGroup({ + @Sql(value = "/sql/delete-all-data.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) +}) public class UserRepositoryTest { @Autowired @@ -25,9 +27,10 @@ public class UserRepositoryTest { // when User savedUser = userRepository.save(user); + User foundUser = userRepository.findByIdOrThrow(savedUser.getId()); // then - Assertions.assertThat(savedUser) + Assertions.assertThat(foundUser) .extracting("name", "phone") .containsExactly(user.getName(), user.getPhone()); } diff --git a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/repository/ApplyRepositoryTest.java b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/repository/ApplyRepositoryTest.java index 3b961566..e261317e 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/repository/ApplyRepositoryTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/repository/ApplyRepositoryTest.java @@ -14,18 +14,18 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyInfoDto; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.context.annotation.Import; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.SqlGroup; +import org.testcontainers.junit.jupiter.Testcontainers; -@DataJpaTest +@SpringBootTest @ActiveProfiles("test") -@Import(TestConfig.class) +@Testcontainers @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @SqlGroup({ @Sql(value = "/sql/apply-repository-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD), diff --git a/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java b/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java index b677c9fa..2783bbe8 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java @@ -1,146 +1,143 @@ package org.sopt.makers.crew.main.post.v2.repository; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; + import org.junit.jupiter.api.Test; -import org.sopt.makers.crew.main.common.config.TestConfig; import org.sopt.makers.crew.main.entity.post.PostRepository; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailResponseDto; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.context.annotation.Import; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.SqlGroup; +import org.testcontainers.junit.jupiter.Testcontainers; -@DataJpaTest +@SpringBootTest @ActiveProfiles("test") -@Import(TestConfig.class) -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Testcontainers @SqlGroup({ - @Sql(value = "/sql/post-repository-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD), - @Sql(value = "/sql/delete-all-data.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD) + @Sql(value = "/sql/post-repository-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD), + @Sql(value = "/sql/delete-all-data.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD) }) public class PostRepositoryTest { - @Autowired - private PostRepository postRepository; - - @Test - void 모임_ID로_필터링해서_게시글_목록_조회() { - // given - int page = 1; - int take = 9; - Integer meetingId = 1; - PostGetPostsCommand queryCommand = new PostGetPostsCommand(meetingId, page, take); - Integer userId = 1; - - // when - Page postDetailJsonDtos = postRepository.findPostList(queryCommand, - PageRequest.of(page - 1, take), - userId); - - // then - assertThat(postDetailJsonDtos.getTotalElements()).isEqualTo(3); - assertThat(postDetailJsonDtos.getSize()).isEqualTo(take); - assertThat(postDetailJsonDtos.getNumber()).isEqualTo(page - 1); - assertThat(postDetailJsonDtos.getTotalPages()).isEqualTo(1); - - PostDetailResponseDto postDetailDto1 = postDetailJsonDtos.getContent().get(0); - assertThat(postDetailDto1) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(3, "제목3", "내용3", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 2, 0)), - 0, false, 0); - assertThat(postDetailDto1.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(1, "스터디 구합니다1", "행사"); - - PostDetailResponseDto postDetailDto2 = postDetailJsonDtos.getContent().get(1); - assertThat(postDetailDto2) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(2, "제목2", "내용2", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 1, 0)), - 0, false, 0); - assertThat(postDetailDto2.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(1, "스터디 구합니다1", "행사"); - - PostDetailResponseDto postDetailDto3 = postDetailJsonDtos.getContent().get(2); - assertThat(postDetailDto3) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(1, "제목1", "내용1", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 0, 0)), - 2, true, 5); - assertThat(postDetailDto3.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(1, "스터디 구합니다1", "행사"); - // 댓글 작성자 최대 3명 순서 검증 - assertThat(postDetailDto3.getCommenterThumbnails()) - .containsExactly("profile1.jpg", "profile2.jpg", "profile3.jpg"); - } - - @Test - void 게시글_목록_전체_조회() { - // given - int page = 1; - int take = 9; - PostGetPostsCommand queryCommand = new PostGetPostsCommand(null, page, take); - Integer userId = 1; - - // when - Page postDetailJsonDtos = postRepository.findPostList(queryCommand, - PageRequest.of(page - 1, take), - userId); - - // then - assertThat(postDetailJsonDtos.getTotalElements()).isEqualTo(5); - assertThat(postDetailJsonDtos.getSize()).isEqualTo(take); - assertThat(postDetailJsonDtos.getNumber()).isEqualTo(page - 1); - assertThat(postDetailJsonDtos.getTotalPages()).isEqualTo(1); - - PostDetailResponseDto postDetailDto1 = postDetailJsonDtos.getContent().get(0); - assertThat(postDetailDto1) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(5, "제목5", "내용5", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 4, 0)), - 0, false, 0); - assertThat(postDetailDto1.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(2, "스터디 구합니다2", "스터디"); - - PostDetailResponseDto postDetailDto2 = postDetailJsonDtos.getContent().get(4); - assertThat(postDetailDto2) - .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") - .containsExactly(1, "제목1", "내용1", - LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 0, 0)), - 2, true, 5); - assertThat(postDetailDto2.getMeeting()) - .extracting("id", "title", "category") - .containsExactly(1, "스터디 구합니다1", "행사"); - } - - @Test - void 게시글_개수_조회() { - // given - int meetingId1 = 1; - int meetingId2 = 2; - - // when - Integer postCount1 = postRepository.countByMeetingId(meetingId1); - Integer postCount2 = postRepository.countByMeetingId(meetingId2); - - // then - assertThat(postCount1).isEqualTo(3); - assertThat(postCount2).isEqualTo(2); - } + @Autowired + private PostRepository postRepository; + + @Test + void 모임_ID로_필터링해서_게시글_목록_조회() { + // given + int page = 1; + int take = 9; + Integer meetingId = 1; + PostGetPostsCommand queryCommand = new PostGetPostsCommand(meetingId, page, take); + Integer userId = 1; + + // when + Page postDetailJsonDtos = postRepository.findPostList(queryCommand, + PageRequest.of(page - 1, take), userId); + + // then + assertThat(postDetailJsonDtos.getTotalElements()).isEqualTo(3); + assertThat(postDetailJsonDtos.getSize()).isEqualTo(take); + assertThat(postDetailJsonDtos.getNumber()).isEqualTo(page - 1); + assertThat(postDetailJsonDtos.getTotalPages()).isEqualTo(1); + + PostDetailResponseDto postDetailDto1 = postDetailJsonDtos.getContent().get(0); + assertThat(postDetailDto1) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(3, "제목3", "내용3", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 2, 0)), + 0, false, 0); + assertThat(postDetailDto1.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(1, "스터디 구합니다1", "행사"); + + PostDetailResponseDto postDetailDto2 = postDetailJsonDtos.getContent().get(1); + assertThat(postDetailDto2) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(2, "제목2", "내용2", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 1, 0)), + 0, false, 0); + assertThat(postDetailDto2.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(1, "스터디 구합니다1", "행사"); + + PostDetailResponseDto postDetailDto3 = postDetailJsonDtos.getContent().get(2); + assertThat(postDetailDto3) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(1, "제목1", "내용1", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 0, 0)), + 2, true, 5); + assertThat(postDetailDto3.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(1, "스터디 구합니다1", "행사"); + // 댓글 작성자 최대 3명 순서 검증 + assertThat(postDetailDto3.getCommenterThumbnails()) + .containsExactly("profile1.jpg", "profile2.jpg", "profile3.jpg"); + } + + @Test + void 게시글_목록_전체_조회() { + // given + int page = 1; + int take = 9; + PostGetPostsCommand queryCommand = new PostGetPostsCommand(null, page, take); + Integer userId = 1; + + // when + Page postDetailJsonDtos = postRepository.findPostList(queryCommand, + PageRequest.of(page - 1, take), + userId); + + // then + assertThat(postDetailJsonDtos.getTotalElements()).isEqualTo(5); + assertThat(postDetailJsonDtos.getSize()).isEqualTo(take); + assertThat(postDetailJsonDtos.getNumber()).isEqualTo(page - 1); + assertThat(postDetailJsonDtos.getTotalPages()).isEqualTo(1); + + PostDetailResponseDto postDetailDto1 = postDetailJsonDtos.getContent().get(0); + assertThat(postDetailDto1) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(5, "제목5", "내용5", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 4, 0)), + 0, false, 0); + assertThat(postDetailDto1.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(2, "스터디 구합니다2", "스터디"); + + PostDetailResponseDto postDetailDto2 = postDetailJsonDtos.getContent().get(4); + assertThat(postDetailDto2) + .extracting("id", "title", "contents", "createdDate", "likeCount", "isLiked", "commentCount") + .containsExactly(1, "제목1", "내용1", + LocalDateTime.of(LocalDate.of(2024, 06, 11), LocalTime.of(10, 0, 0, 0)), + 2, true, 5); + assertThat(postDetailDto2.getMeeting()) + .extracting("id", "title", "category") + .containsExactly(1, "스터디 구합니다1", "행사"); + } + + @Test + void 게시글_개수_조회() { + // given + int meetingId1 = 1; + int meetingId2 = 2; + + // when + Integer postCount1 = postRepository.countByMeetingId(meetingId1); + Integer postCount2 = postRepository.countByMeetingId(meetingId2); + + // then + assertThat(postCount1).isEqualTo(3); + assertThat(postCount2).isEqualTo(2); + } } diff --git a/main/src/test/resources/sql/apply-repository-test-data.sql b/main/src/test/resources/sql/apply-repository-test-data.sql index 3ddff246..00c2525e 100644 --- a/main/src/test/resources/sql/apply-repository-test-data.sql +++ b/main/src/test/resources/sql/apply-repository-test-data.sql @@ -1,5 +1,3 @@ -CREATE TYPE meeting_joinableparts_enum AS ENUM ('PM', 'DESIGN', 'IOS', 'ANDROID', 'SERVER', 'WEB'); - INSERT INTO "user" (id, name, "orgId", activities, "profileImage", phone) VALUES (1, '김삼순', 1001, '[{"part": "서버", "generation": 33}, {"part": "iOS", "generation": 32}]', @@ -14,30 +12,6 @@ VALUES (1, '김삼순', 1001, '[{"part": "iOS", "generation": 32}, {"part": "안드로이드", "generation": 29}]', 'profile4.jpg', '010-5555-5555'); -create table "meeting" ( - "canJoinOnlyActiveGeneration" boolean not null, - "capacity" integer not null, - "createdGeneration" integer not null, - "id" serial not null, - "isMentorNeeded" boolean not null, - "targetActiveGeneration" integer, - "userId" integer, - "endDate" TIMESTAMP not null, - "mEndDate" TIMESTAMP not null, - "mStartDate" TIMESTAMP not null, - "startDate" TIMESTAMP not null, - "category" varchar(255) not null, - "desc" varchar(255) not null, - "leaderDesc" varchar(255) not null, - "note" varchar(255), - "processDesc" varchar(255) not null, - "targetDesc" varchar(255) not null, - "title" varchar(255) not null, - "imageURL" jsonb, - "joinableParts" meeting_joinableparts_enum[], - primary key ("id") -); - INSERT INTO meeting (id, "userId", title, category, "imageURL", "startDate", "endDate", capacity, "desc", "processDesc", "mStartDate", "mEndDate", "leaderDesc", "targetDesc", note, "isMentorNeeded", "canJoinOnlyActiveGeneration", "createdGeneration", diff --git a/main/src/test/resources/sql/delete-all-data.sql b/main/src/test/resources/sql/delete-all-data.sql index 637ccb04..493bd583 100644 --- a/main/src/test/resources/sql/delete-all-data.sql +++ b/main/src/test/resources/sql/delete-all-data.sql @@ -2,9 +2,9 @@ DELETE FROM "apply"; DELETE FROM "comment"; DELETE FROM "like"; +DELETE FROM "post"; DELETE FROM "meeting"; DELETE FROM "notice"; -DELETE FROM "post"; DELETE FROM "user"; -- 시퀀스 초기화 diff --git a/main/src/test/resources/sql/post-repository-test-data.sql b/main/src/test/resources/sql/post-repository-test-data.sql index e2676f94..f381c0bb 100644 --- a/main/src/test/resources/sql/post-repository-test-data.sql +++ b/main/src/test/resources/sql/post-repository-test-data.sql @@ -1,5 +1,3 @@ -CREATE TYPE meeting_joinableparts_enum AS ENUM ('PM', 'DESIGN', 'IOS', 'ANDROID', 'SERVER', 'WEB'); - INSERT INTO "user" (id, name, "orgId", activities, "profileImage", phone) VALUES (1, '김삼순', 1001, '[{"part": "서버", "generation": 33}, {"part": "iOS", "generation": 32}]', @@ -14,30 +12,6 @@ VALUES (1, '김삼순', 1001, '[{"part": "iOS", "generation": 32}, {"part": "안드로이드", "generation": 29}]', 'profile4.jpg', '010-5555-5555'); -create table "meeting" ( - "canJoinOnlyActiveGeneration" boolean not null, - "capacity" integer not null, - "createdGeneration" integer not null, - "id" serial not null, - "isMentorNeeded" boolean not null, - "targetActiveGeneration" integer, - "userId" integer, - "endDate" TIMESTAMP not null, - "mEndDate" TIMESTAMP not null, - "mStartDate" TIMESTAMP not null, - "startDate" TIMESTAMP not null, - "category" varchar(255) not null, - "desc" varchar(255) not null, - "leaderDesc" varchar(255) not null, - "note" varchar(255), - "processDesc" varchar(255) not null, - "targetDesc" varchar(255) not null, - "title" varchar(255) not null, - "imageURL" jsonb, - "joinableParts" meeting_joinableparts_enum[], - primary key ("id") -); - INSERT INTO meeting (id, "userId", title, category, "imageURL", "startDate", "endDate", capacity, "desc", "processDesc", "mStartDate", "mEndDate", "leaderDesc", "targetDesc", note, "isMentorNeeded", "canJoinOnlyActiveGeneration", "createdGeneration", From ffae9c91ec4e400ac19d17e8b53f2fc1a8914a47 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Sun, 22 Sep 2024 23:01:40 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=97=90=EC=84=9C=20=EC=B0=A8=EB=8B=A8=EB=90=9C=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=ED=95=84=ED=84=B0=EB=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#392)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * del: 임시 코드 삭제 * chore(comment): 차단 여부 필드 추가 * feat(comment): 차단 유저 Map 반환 메서드 구현 * feat(comment): 차단 댓글 필터링 구현 * fix(comment): 삭제된 댓글에 대한 처리 --- .../comment/v2/dto/response/CommentDto.java | 12 +++++-- .../comment/v2/dto/response/ReplyDto.java | 11 ++++-- .../v2/service/CommentV2ServiceImpl.java | 36 +++++++++++++++---- .../main/external/playground/TestService.java | 26 -------------- .../service/MemberBlockService.java | 2 ++ .../service/MemberBlockServiceImpl.java | 8 +++++ 6 files changed, 57 insertions(+), 38 deletions(-) delete mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/playground/TestService.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java index 7c48f1f4..49d0dc97 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java @@ -54,9 +54,13 @@ public class CommentDto { @NotNull private final List replies; + @Schema(description = "차단여부", example = "false") + @NotNull + private final Boolean isBlockedComment; + @QueryProjection public CommentDto(Integer id, String contents, CommentWriterDto user, LocalDateTime createdDate, int likeCount, - boolean isLiked, boolean isWriter, int order, List replies) { + boolean isLiked, boolean isWriter, int order, List replies, Boolean isBlockedComment) { this.id = id; this.contents = contents; this.user = user; @@ -66,9 +70,11 @@ public CommentDto(Integer id, String contents, CommentWriterDto user, LocalDateT this.isWriter = isWriter; this.order = order; this.replies = replies; + this.isBlockedComment = isBlockedComment; } - public static CommentDto of(Comment comment, boolean isLiked, boolean isWriter, List replies) { + public static CommentDto of(Comment comment, boolean isLiked, boolean isWriter, List replies, + Boolean isBlockedComment) { Integer userId = comment.getUser() == null ? null : comment.getUser().getId(); Integer orgId = comment.getUser() == null ? null : comment.getUser().getOrgId(); String userName = comment.getUser() == null ? null : comment.getUser().getName(); @@ -77,6 +83,6 @@ public static CommentDto of(Comment comment, boolean isLiked, boolean isWriter, return new CommentDto(comment.getId(), comment.getContents(), new CommentWriterDto(userId, orgId, userName, profileImage), comment.getCreatedDate(), comment.getLikeCount(), - isLiked, isWriter, comment.getOrder(), replies); + isLiked, isWriter, comment.getOrder(), replies, isBlockedComment); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java index 479c1986..cf72f3ff 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java @@ -43,9 +43,13 @@ public class ReplyDto { @NotNull private final int order; + @Schema(description = "차단여부", example = "false") + @NotNull + private final Boolean isBlockedComment; + @QueryProjection public ReplyDto(Integer id, String contents, CommentWriterDto user, LocalDateTime createdDate, int likeCount, - Boolean isLiked, Boolean isWriter, int order) { + Boolean isLiked, Boolean isWriter, int order, Boolean isBlockedComment) { this.id = id; this.contents = contents; this.user = user; @@ -54,12 +58,13 @@ public ReplyDto(Integer id, String contents, CommentWriterDto user, LocalDateTim this.isLiked = isLiked; this.isWriter = isWriter; this.order = order; + this.isBlockedComment = isBlockedComment; } - public static ReplyDto of(Comment comment, boolean isLiked, boolean isWriter) { + public static ReplyDto of(Comment comment, boolean isLiked, boolean isWriter, boolean isBlockedComment) { return new ReplyDto(comment.getId(), comment.getContents(), new CommentWriterDto(comment.getUser().getId(), comment.getUser().getOrgId(), comment.getUser().getName(), comment.getUser().getProfileImage()), comment.getCreatedDate(), comment.getLikeCount(), - isLiked, isWriter, comment.getOrder()); + isLiked, isWriter, comment.getOrder(), isBlockedComment); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java index 664a756c..00fccbac 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java @@ -41,6 +41,7 @@ import org.sopt.makers.crew.main.entity.report.ReportRepository; import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.entity.user.UserRepository; +import org.sopt.makers.crew.main.external.playground.service.MemberBlockService; import org.sopt.makers.crew.main.internal.notification.PushNotificationService; import org.sopt.makers.crew.main.internal.notification.dto.PushNotificationRequestDto; import org.springframework.beans.factory.annotation.Value; @@ -60,6 +61,8 @@ public class CommentV2ServiceImpl implements CommentV2Service { private final ReportRepository reportRepository; private final LikeRepository likeRepository; + private final MemberBlockService memberBlockService; + private final PushNotificationService pushNotificationService; private final CommentMapper commentMapper; @@ -168,21 +171,42 @@ public CommentV2GetCommentsResponseDto getComments(Integer postId, Integer page, List comments = commentRepository.findAllByPostIdOrderByCreatedDate(postId); + User user = userRepository.findByIdOrThrow(userId); + Long orgId = user.getOrgId().longValue(); + + Map blockedUsers = memberBlockService.getBlockedUsers(orgId); + MyLikes myLikes = new MyLikes(likeRepository.findAllByUserIdAndCommentIdNotNull(userId)); Map> replyMap = new HashMap<>(); comments.stream() .filter(comment -> !comment.isParentComment()) .forEach( - comment -> replyMap.computeIfAbsent(comment.getParentId(), k -> new ArrayList<>()) - .add(ReplyDto.of(comment, myLikes.isLikeComment(comment.getId()), - comment.isWriter(userId)))); + comment -> { + boolean isBlockedComment = false; + if (comment.getUserId() != null) { + isBlockedComment = blockedUsers.getOrDefault(comment.getUser().getOrgId().longValue(), + false); + } + + replyMap.computeIfAbsent(comment.getParentId(), k -> new ArrayList<>()) + .add(ReplyDto.of(comment, myLikes.isLikeComment(comment.getId()), + comment.isWriter(userId), isBlockedComment)); + }); List commentDtos = comments.stream() .filter(Comment::isParentComment) - .map(comment -> CommentDto.of(comment, myLikes.isLikeComment(comment.getId()), - comment.isWriter(userId), - replyMap.get(comment.getId()))) + .map(comment -> { + boolean isBlockedComment = false; + if (comment.getUserId() != null) { + isBlockedComment = blockedUsers.getOrDefault(comment.getUser().getOrgId().longValue(), + false); + } + + return CommentDto.of(comment, myLikes.isLikeComment(comment.getId()), + comment.isWriter(userId), + replyMap.get(comment.getId()), isBlockedComment); + }) .toList(); PageMetaDto pageMetaDto = new PageMetaDto(new PageOptionsDto(1, 12), 30); diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/TestService.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/TestService.java deleted file mode 100644 index 1c34d67e..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/external/playground/TestService.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.sopt.makers.crew.main.external.playground; - -import java.util.List; - -import org.sopt.makers.crew.main.external.playground.entity.member_block.MemberBlock; -import org.sopt.makers.crew.main.external.playground.entity.member_block.MemberBlockRepository; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import lombok.RequiredArgsConstructor; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/test") -public class TestService { - private final MemberBlockRepository memberBlockRepository; - - @GetMapping - public List test1() { - // TODO: 해당 API는 차단 기능 구현할 때 참고용으로 만들었으며, 삭제가 필요하다. - List all = memberBlockRepository.findAll(); - - return all; - } -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java index d125cf78..6de4bf18 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockService.java @@ -5,4 +5,6 @@ public interface MemberBlockService { Map getBlockedUsers(Long blockerOrgId, List userOrgIds); + + Map getBlockedUsers(Long blockerOrgId); } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java index 9d600a0e..6b96da29 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/service/MemberBlockServiceImpl.java @@ -24,4 +24,12 @@ public Map getBlockedUsers(Long blockerOrgId, List userOrgI .filter(memberBlock -> userOrgIds.contains(memberBlock.getBlockedMember())) .collect(Collectors.toMap(MemberBlock::getBlockedMember, memberBlock -> true)); } + + @Override + public Map getBlockedUsers(Long blockerOrgId) { + List memberBlocks = memberBlockRepository.findAllByBlockerAndIsBlockedTrue(blockerOrgId); + + return memberBlocks.stream() + .collect(Collectors.toMap(MemberBlock::getBlockedMember, memberBlock -> true)); + } } \ No newline at end of file From d68fec767a74a1c46ec6b5a2741db0604802ed8e Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Mon, 23 Sep 2024 00:16:39 +0900 Subject: [PATCH 5/9] =?UTF-8?q?fix(Meeting):=20=EC=8B=A0=EC=B2=AD=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EA=B0=80=20=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=9E=91=EC=97=85=20(#390)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/main/entity/apply/Applies.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java index b4cf5af0..b7aae643 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java @@ -28,6 +28,10 @@ public Applies(List applies) { } public int getAppliedCount(Integer meetingId) { + if (!hasApplies(meetingId)) { + return 0; + } + List applies = appliesMap.get(meetingId); if (applies == null) { @@ -37,18 +41,26 @@ public int getAppliedCount(Integer meetingId) { } public int getApprovedCount(Integer meetingId) { + if (!hasApplies(meetingId)) { + return 0; + } + List applies = appliesMap.get(meetingId); if (applies == null) { return 0; } - return (int) appliesMap.get(meetingId).stream() + return (int)appliesMap.get(meetingId).stream() .filter(apply -> apply.getStatus().equals(EnApplyStatus.APPROVE)) .count(); } public Boolean isApply(Integer meetingId, Integer userId) { + if (!hasApplies(meetingId)) { + return false; + } + List applies = appliesMap.get(meetingId); if (applies == null) { @@ -60,6 +72,10 @@ public Boolean isApply(Integer meetingId, Integer userId) { } public Boolean isApproved(Integer meetingId, Integer userId) { + if (!hasApplies(meetingId)) { + return false; + } + List applies = appliesMap.get(meetingId); if (applies == null) { @@ -70,4 +86,8 @@ public Boolean isApproved(Integer meetingId, Integer userId) { .anyMatch(apply -> apply.getUserId().equals(userId) && apply.getStatus().equals(EnApplyStatus.APPROVE)); } + + private boolean hasApplies(Integer meetingId) { + return appliesMap.containsKey(meetingId); + } } From 5fa58f015d14d2617f424ecd6abbfed17c80f21f Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Mon, 23 Sep 2024 01:16:02 +0900 Subject: [PATCH 6/9] =?UTF-8?q?fix(Meeting):=20=EC=8B=A0=EC=B2=AD=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=B2=98=EB=A6=AC=20(#394)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/makers/crew/main/entity/apply/Applies.java | 2 +- .../meeting/v2/service/MeetingV2ServiceImpl.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java index b7aae643..19766095 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java @@ -87,7 +87,7 @@ public Boolean isApproved(Integer meetingId, Integer userId) { && apply.getStatus().equals(EnApplyStatus.APPROVE)); } - private boolean hasApplies(Integer meetingId) { + public boolean hasApplies(Integer meetingId) { return appliesMap.containsKey(meetingId); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index 27e937c0..0af1a27f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -165,7 +165,8 @@ private List getResponseDto(List Map postMap, Applies applies) { return meetings.stream() .map(meeting -> { - MeetingV2GetMeetingBannerResponseUserDto meetingCreatorDto = MeetingV2GetMeetingBannerResponseUserDto.of(meeting.getUser()); + MeetingV2GetMeetingBannerResponseUserDto meetingCreatorDto = MeetingV2GetMeetingBannerResponseUserDto.of( + meeting.getUser()); LocalDateTime recentActivityDate = null; if (postMap.containsKey(meeting.getId())) { @@ -361,9 +362,12 @@ public MeetingV2GetMeetingByIdResponseDto getMeetingById(Integer meetingId, Inte Boolean isApproved = applies.isApproved(meetingId, user.getId()); long approvedCount = applies.getApprovedCount(meetingId); - List applyWholeInfoDtos = applies.getAppliesMap().get(meetingId).stream() - .map(apply -> ApplyWholeInfoDto.of(apply, apply.getUser(), userId)) - .toList(); + List applyWholeInfoDtos = new ArrayList<>(); + if (applies.hasApplies(meetingId)) { + applyWholeInfoDtos = applies.getAppliesMap().get(meetingId).stream() + .map(apply -> ApplyWholeInfoDto.of(apply, apply.getUser(), userId)) + .toList(); + } return MeetingV2GetMeetingByIdResponseDto.of(meeting, approvedCount, isHost, isApply, isApproved, meetingCreator, applyWholeInfoDtos, time.now()); From 43a249283a9b58271939c4ad2908168742512da1 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Mon, 23 Sep 2024 01:52:33 +0900 Subject: [PATCH 7/9] =?UTF-8?q?=08chore:=20=ED=98=84=EC=9E=AC=20=EA=B8=B0?= =?UTF-8?q?=EC=88=98=2035=EA=B8=B0=EB=A1=9C=20=EC=88=98=EC=A0=95=20(#397)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(const): final 클래스로 변경 * chore(const): 현재 기수 35기로 수정 * chore(const): 생성자 -> lombok 으로 수정 --- .../crew/main/common/constant/CrewConst.java | 25 +++++++++++-------- .../constant/active-generation.const.ts | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/constant/CrewConst.java b/main/src/main/java/org/sopt/makers/crew/main/common/constant/CrewConst.java index 67805790..2e8e5cc7 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/constant/CrewConst.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/constant/CrewConst.java @@ -1,19 +1,22 @@ package org.sopt.makers.crew.main.common.constant; -public abstract class CrewConst { +import lombok.AccessLevel; +import lombok.NoArgsConstructor; - /** - * 매 기수 시작하기 전에 수정 필요 - * */ - public static final Integer ACTIVE_GENERATION = 34; +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class CrewConst { - public static final String DAY_START_TIME = " 00:00:00"; - public static final String DAY_END_TIME = " 23:59:59"; + /** + * 매 기수 시작하기 전에 수정 필요 + * */ + public static final Integer ACTIVE_GENERATION = 35; - public static final String DAY_FORMAT = "yyyy.MM.dd"; - public static final String DAY_TIME_FORMAT = "yyyy.MM.dd HH:mm:ss"; + public static final String DAY_START_TIME = " 00:00:00"; + public static final String DAY_END_TIME = " 23:59:59"; - public static final String ORDER_ASC = "asc"; - public static final String ORDER_DESC = "desc"; + public static final String DAY_FORMAT = "yyyy.MM.dd"; + public static final String DAY_TIME_FORMAT = "yyyy.MM.dd HH:mm:ss"; + public static final String ORDER_ASC = "asc"; + public static final String ORDER_DESC = "desc"; } diff --git a/server/src/common/constant/active-generation.const.ts b/server/src/common/constant/active-generation.const.ts index 54c9059e..a65a635e 100644 --- a/server/src/common/constant/active-generation.const.ts +++ b/server/src/common/constant/active-generation.const.ts @@ -2,4 +2,4 @@ * 활동 기수 * TODO: 2월 1일, 8월 1일 기준으로 1씩 단조증가하게 만들기 */ -export const ACTIVE_GENERATION = 34; +export const ACTIVE_GENERATION = 35; From e85d4001e1004a7b99925b6ae2ee1bbffddc35b0 Mon Sep 17 00:00:00 2001 From: DongHoon Lee <125895298+hoonyworld@users.noreply.github.com> Date: Mon, 23 Sep 2024 01:53:32 +0900 Subject: [PATCH 8/9] =?UTF-8?q?fix(PostDetailResponseDto):=20Lombok=20gett?= =?UTF-8?q?er=EC=97=90=EC=84=9C=20isBlockedPost=EB=A5=BC=20=EC=83=9D?= =?UTF-8?q?=EB=9E=B5=20=ED=9B=84=20=EC=A7=81=EC=A0=91=20getter=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#398)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/post/v2/dto/response/PostDetailResponseDto.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java index fd35d954..238d7e41 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java @@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -63,6 +64,7 @@ public class PostDetailResponseDto { @Schema(description = "차단된 유저의 게시물인지 여부", example = "false") @NotNull + @Getter(AccessLevel.NONE) private final boolean isBlockedPost; public static PostDetailResponseDto of(PostDetailBaseDto postDetail, @@ -72,4 +74,8 @@ public static PostDetailResponseDto of(PostDetailBaseDto postDetail, postDetail.getIsLiked(), postDetail.getViewCount(), postDetail.getCommentCount(), postDetail.getMeeting(), postTopCommenterThumbnails.getCommenterThumbnails(), isBlockedPost); } + + public boolean getIsBlockedPost() { + return isBlockedPost; + } } \ No newline at end of file From 2219c368e9963f533e7cc89a1f101a611087b0b2 Mon Sep 17 00:00:00 2001 From: DongHoon Lee <125895298+hoonyworld@users.noreply.github.com> Date: Mon, 23 Sep 2024 02:14:17 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor:=20isBlockedComment=EB=A5=BC=20Lom?= =?UTF-8?q?bok=20getter=EC=97=90=EC=84=9C=20=EC=83=9D=EB=9E=B5=20=ED=9B=84?= =?UTF-8?q?=20=EC=A7=81=EC=A0=91=20getter=20=EA=B5=AC=ED=98=84=20=20(#400)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(CommentDto): Lombok getter에서 isBlockedComment를 생략 후 직접 getter 구현 * refactor(ReplyDto): Lombok getter에서 isBlockedComment를 생략 후 직접 getter 구현 --- .../main/comment/v2/dto/response/CommentDto.java | 15 +++++++++------ .../main/comment/v2/dto/response/ReplyDto.java | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java index 49d0dc97..281f4637 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java @@ -2,16 +2,14 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; import org.sopt.makers.crew.main.entity.comment.Comment; -import org.sopt.makers.crew.main.entity.user.User; -import com.fasterxml.jackson.annotation.JsonProperty; import com.querydsl.core.annotations.QueryProjection; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Getter; @Getter @@ -55,12 +53,13 @@ public class CommentDto { private final List replies; @Schema(description = "차단여부", example = "false") + @Getter(AccessLevel.NONE) @NotNull - private final Boolean isBlockedComment; + private final boolean isBlockedComment; @QueryProjection public CommentDto(Integer id, String contents, CommentWriterDto user, LocalDateTime createdDate, int likeCount, - boolean isLiked, boolean isWriter, int order, List replies, Boolean isBlockedComment) { + boolean isLiked, boolean isWriter, int order, List replies, boolean isBlockedComment) { this.id = id; this.contents = contents; this.user = user; @@ -74,7 +73,7 @@ public CommentDto(Integer id, String contents, CommentWriterDto user, LocalDateT } public static CommentDto of(Comment comment, boolean isLiked, boolean isWriter, List replies, - Boolean isBlockedComment) { + boolean isBlockedComment) { Integer userId = comment.getUser() == null ? null : comment.getUser().getId(); Integer orgId = comment.getUser() == null ? null : comment.getUser().getOrgId(); String userName = comment.getUser() == null ? null : comment.getUser().getName(); @@ -85,4 +84,8 @@ public static CommentDto of(Comment comment, boolean isLiked, boolean isWriter, profileImage), comment.getCreatedDate(), comment.getLikeCount(), isLiked, isWriter, comment.getOrder(), replies, isBlockedComment); } + + public boolean getIsBlockedComment() { + return isBlockedComment; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java index cf72f3ff..e9c783ba 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java @@ -1,10 +1,14 @@ package org.sopt.makers.crew.main.comment.v2.dto.response; import java.time.LocalDateTime; + import org.sopt.makers.crew.main.entity.comment.Comment; + import com.querydsl.core.annotations.QueryProjection; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; import lombok.Getter; @Getter @@ -44,12 +48,13 @@ public class ReplyDto { private final int order; @Schema(description = "차단여부", example = "false") + @Getter(AccessLevel.NONE) @NotNull - private final Boolean isBlockedComment; + private final boolean isBlockedComment; @QueryProjection public ReplyDto(Integer id, String contents, CommentWriterDto user, LocalDateTime createdDate, int likeCount, - Boolean isLiked, Boolean isWriter, int order, Boolean isBlockedComment) { + Boolean isLiked, Boolean isWriter, int order, boolean isBlockedComment) { this.id = id; this.contents = contents; this.user = user; @@ -61,10 +66,14 @@ public ReplyDto(Integer id, String contents, CommentWriterDto user, LocalDateTim this.isBlockedComment = isBlockedComment; } - public static ReplyDto of(Comment comment, boolean isLiked, boolean isWriter, boolean isBlockedComment) { + public static ReplyDto of(Comment comment, boolean isLiked, boolean isWriter, boolean isBlockedComment) { return new ReplyDto(comment.getId(), comment.getContents(), new CommentWriterDto(comment.getUser().getId(), comment.getUser().getOrgId(), comment.getUser().getName(), comment.getUser().getProfileImage()), comment.getCreatedDate(), comment.getLikeCount(), isLiked, isWriter, comment.getOrder(), isBlockedComment); } + + public boolean getIsBlockedComment() { + return isBlockedComment; + } }