From 92fe5d1538c31da2c93673d370206a8ecc034ef9 Mon Sep 17 00:00:00 2001 From: Cho Sangwook <82208159+Sangwook02@users.noreply.github.com> Date: Tue, 5 Mar 2024 20:26:02 +0900 Subject: [PATCH 01/11] =?UTF-8?q?chore:=20CI=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95=20(#257)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chore: ci 워크플로우 수정 --- .../workflows/pull_request_gradle_build.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pull_request_gradle_build.yml b/.github/workflows/pull_request_gradle_build.yml index c01e2b432..c88599782 100644 --- a/.github/workflows/pull_request_gradle_build.yml +++ b/.github/workflows/pull_request_gradle_build.yml @@ -4,13 +4,16 @@ on: pull_request: branches: ["develop"] +permissions: + pull-requests: write + jobs: build-test: runs-on: ubuntu-latest steps: - name: Git Checkout - uses: actions/checkout@v3.0.2 + uses: actions/checkout@v4 - name: JDK 설치 uses: actions/setup-java@v4 @@ -25,8 +28,13 @@ jobs: - name: Start containers run: docker-compose -f ./docker-compose-test.yaml up -d - - name: Gradle Build - uses: gradle/gradle-build-action@v2 + - name: Build with Gradle + id: gradle + uses: gradle/actions/setup-gradle@v3 with: - arguments: check - cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} + arguments: | + build + --configuration-cache + cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} # feature 브랜치는 캐시를 읽기 전용으로 설정 + cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} + add-job-summary-as-pr-comment: always From 7b8753c9326f097562d651b15c04b2de57bf5ed9 Mon Sep 17 00:00:00 2001 From: Jaehyun Ahn <91878695+uwoobeat@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:53:43 +0900 Subject: [PATCH 02/11] =?UTF-8?q?hotfix:=20Basic=20Auth=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EB=B3=80=EC=88=98=20=EC=86=8D=EC=84=B1=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95=20(#261)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * hotfix: 이메일 정규식에 언더스코어를 허용하도록 수정 (#205) * fix: 언더스코어 허용하도록 변경 * refactor: 테스트 접근제어자 제거 * fix: 환경변수 속성 이름 오타 수정 * refactor: Basic Auth 환경변수 이름 재수정 --------- Co-authored-by: Cho Sangwook <82208159+Sangwook02@users.noreply.github.com> --- src/main/resources/application-security.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application-security.yml b/src/main/resources/application-security.yml index 56531e408..bbdb261ff 100644 --- a/src/main/resources/application-security.yml +++ b/src/main/resources/application-security.yml @@ -23,5 +23,5 @@ jwt: issuer: ${JWT_ISSUER:} auth: - username: ${SWAGGER_USER:default} - password: ${SWAGGER_PASSWORD:default} + username: ${BASIC_AUTH_USERNAME:default} + password: ${BASIC_AUTH_PASSWORD:default} From 4b5643e98d1816332a056c832d5f45eef2a1ea0e Mon Sep 17 00:00:00 2001 From: Cho Sangwook <82208159+Sangwook02@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:33:59 +0900 Subject: [PATCH 03/11] =?UTF-8?q?refactor:=20Soft=20Delete=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EC=A1=B0=EA=B1=B4=EC=9D=84=20@SQLRestriction?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=8C=80=EC=B2=B4=20(#263)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: soft delete에 대한 조건을 어노테이션으로 대체 * remove: 사용하지 않는 메서드 제거 * test: 테스트가 기대하는 예외 변경 --- .../dao/MemberCustomRepositoryImpl.java | 35 ++++--------------- .../gdsc/domain/member/domain/Member.java | 2 ++ .../application/AdminMemberServiceTest.java | 2 +- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java index feebf0633..6961c4e86 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java @@ -7,7 +7,6 @@ import com.gdschongik.gdsc.domain.member.domain.Department; import com.gdschongik.gdsc.domain.member.domain.Member; import com.gdschongik.gdsc.domain.member.domain.MemberRole; -import com.gdschongik.gdsc.domain.member.domain.MemberStatus; import com.gdschongik.gdsc.domain.member.domain.RequirementStatus; import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryRequest; import com.querydsl.core.BooleanBuilder; @@ -33,21 +32,15 @@ public class MemberCustomRepositoryImpl implements MemberCustomRepository { @Override public Optional findNormalByOauthId(String oauthId) { - return Optional.ofNullable(queryFactory - .selectFrom(member) - .where(eqOauthId(oauthId), eqStatus(MemberStatus.NORMAL)) - .fetchOne()); + return Optional.ofNullable( + queryFactory.selectFrom(member).where(eqOauthId(oauthId)).fetchOne()); } @Override public Page findAllGrantable(MemberQueryRequest queryRequest, Pageable pageable) { List fetch = queryFactory .selectFrom(member) - .where( - queryOption(queryRequest), - eqStatus(MemberStatus.NORMAL), - eqRole(MemberRole.GUEST), - requirementVerified()) + .where(queryOption(queryRequest), eqRole(MemberRole.GUEST), requirementVerified()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .orderBy(member.createdAt.desc()) @@ -56,11 +49,7 @@ public Page findAllGrantable(MemberQueryRequest queryRequest, Pageable p JPAQuery countQuery = queryFactory .select(member.count()) .from(member) - .where( - queryOption(queryRequest), - eqStatus(MemberStatus.NORMAL), - eqRole(MemberRole.GUEST), - requirementVerified()); + .where(queryOption(queryRequest), eqRole(MemberRole.GUEST), requirementVerified()); return PageableExecutionUtils.getPage(fetch, pageable, countQuery::fetchOne); } @@ -69,7 +58,7 @@ public Page findAllGrantable(MemberQueryRequest queryRequest, Pageable p public Page findAllByRole(MemberQueryRequest queryRequest, Pageable pageable, @Nullable MemberRole role) { List fetch = queryFactory .selectFrom(member) - .where(queryOption(queryRequest), eqRole(role), eqStatus(MemberStatus.NORMAL), isStudentIdNotNull()) + .where(queryOption(queryRequest), eqRole(role), isStudentIdNotNull()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .orderBy(member.createdAt.desc()) @@ -78,7 +67,7 @@ public Page findAllByRole(MemberQueryRequest queryRequest, Pageable page JPAQuery countQuery = queryFactory .select(member.count()) .from(member) - .where(queryOption(queryRequest), eqRole(role), eqStatus(MemberStatus.NORMAL), isStudentIdNotNull()); + .where(queryOption(queryRequest), eqRole(role), isStudentIdNotNull()); return PageableExecutionUtils.getPage(fetch, pageable, countQuery::fetchOne); } @@ -90,7 +79,6 @@ public Page findAllByPaymentStatus( .selectFrom(member) .where( queryOption(queryRequest), - eqStatus(MemberStatus.NORMAL), eqRequirementStatus(member.requirement.paymentStatus, paymentStatus), isStudentIdNotNull()) .offset(pageable.getOffset()) @@ -103,7 +91,6 @@ public Page findAllByPaymentStatus( .from(member) .where( queryOption(queryRequest), - eqStatus(MemberStatus.NORMAL), eqRequirementStatus(member.requirement.paymentStatus, paymentStatus), isStudentIdNotNull()); @@ -124,7 +111,7 @@ public Map> groupByVerified(List memberIdList) { public List findAllByRole(MemberRole role) { return queryFactory .selectFrom(member) - .where(eqRole(role), eqStatus(MemberStatus.NORMAL), isStudentIdNotNull()) + .where(eqRole(role), isStudentIdNotNull()) .orderBy(member.studentId.asc(), member.name.asc()) .fetch(); } @@ -154,18 +141,10 @@ private BooleanExpression eqRequirementStatus( return requirementStatus != null ? requirement.eq(requirementStatus) : null; } - private BooleanExpression eqId(Long id) { - return member.id.eq(id); - } - private BooleanExpression eqOauthId(String oauthId) { return member.oauthId.eq(oauthId); } - private BooleanExpression eqStatus(MemberStatus status) { - return member.status.eq(status); - } - private BooleanBuilder queryOption(MemberQueryRequest queryRequest) { BooleanBuilder booleanBuilder = new BooleanBuilder(); diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java b/src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java index 6273b3793..1a0424525 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java @@ -18,9 +18,11 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLRestriction; @Entity @Getter +@SQLRestriction("status='NORMAL'") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Member extends BaseTimeEntity { diff --git a/src/test/java/com/gdschongik/gdsc/domain/member/application/AdminMemberServiceTest.java b/src/test/java/com/gdschongik/gdsc/domain/member/application/AdminMemberServiceTest.java index 65b349fad..1a8114bf6 100644 --- a/src/test/java/com/gdschongik/gdsc/domain/member/application/AdminMemberServiceTest.java +++ b/src/test/java/com/gdschongik/gdsc/domain/member/application/AdminMemberServiceTest.java @@ -34,6 +34,6 @@ class AdminMemberServiceTest { "A111111", "name", "010-1234-5678", Department.D001, "email@email.com", "discordUsername", "한글"); assertThatThrownBy(() -> adminMemberService.updateMember(member.getId(), requestBody)) .isInstanceOf(CustomException.class) - .hasMessage(ErrorCode.MEMBER_DELETED.getMessage()); + .hasMessage(ErrorCode.MEMBER_NOT_FOUND.getMessage()); } } From 4b0e300d954ba951f3d943d5af13cb8eca55ab18 Mon Sep 17 00:00:00 2001 From: Jaehyun Ahn <91878695+uwoobeat@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:55:09 +0900 Subject: [PATCH 04/11] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=20=ED=94=84=EB=A1=9C=ED=8D=BC=ED=8B=B0=EC=9D=98=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?(#265)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 이메일 프로퍼티 패키지 변경 --- .../gdschongik/gdsc/global/config/JavaMailSenderConfig.java | 4 ++-- .../com/gdschongik/gdsc/global/config/PropertyConfig.java | 2 +- .../gdsc/global/property/{email => }/EmailProperty.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/main/java/com/gdschongik/gdsc/global/property/{email => }/EmailProperty.java (91%) diff --git a/src/main/java/com/gdschongik/gdsc/global/config/JavaMailSenderConfig.java b/src/main/java/com/gdschongik/gdsc/global/config/JavaMailSenderConfig.java index ea5d21267..7f1b3ff6b 100644 --- a/src/main/java/com/gdschongik/gdsc/global/config/JavaMailSenderConfig.java +++ b/src/main/java/com/gdschongik/gdsc/global/config/JavaMailSenderConfig.java @@ -1,7 +1,7 @@ package com.gdschongik.gdsc.global.config; -import com.gdschongik.gdsc.global.property.email.EmailProperty; -import com.gdschongik.gdsc.global.property.email.EmailProperty.Gmail; +import com.gdschongik.gdsc.global.property.EmailProperty; +import com.gdschongik.gdsc.global.property.EmailProperty.Gmail; import java.util.Properties; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/com/gdschongik/gdsc/global/config/PropertyConfig.java b/src/main/java/com/gdschongik/gdsc/global/config/PropertyConfig.java index 107edc5fe..b03a1b5d2 100644 --- a/src/main/java/com/gdschongik/gdsc/global/config/PropertyConfig.java +++ b/src/main/java/com/gdschongik/gdsc/global/config/PropertyConfig.java @@ -2,9 +2,9 @@ import com.gdschongik.gdsc.global.property.BasicAuthProperty; import com.gdschongik.gdsc.global.property.DiscordProperty; +import com.gdschongik.gdsc.global.property.EmailProperty; import com.gdschongik.gdsc.global.property.JwtProperty; import com.gdschongik.gdsc.global.property.RedisProperty; -import com.gdschongik.gdsc.global.property.email.EmailProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/gdschongik/gdsc/global/property/email/EmailProperty.java b/src/main/java/com/gdschongik/gdsc/global/property/EmailProperty.java similarity index 91% rename from src/main/java/com/gdschongik/gdsc/global/property/email/EmailProperty.java rename to src/main/java/com/gdschongik/gdsc/global/property/EmailProperty.java index f66ca581a..03a6f9f05 100644 --- a/src/main/java/com/gdschongik/gdsc/global/property/email/EmailProperty.java +++ b/src/main/java/com/gdschongik/gdsc/global/property/EmailProperty.java @@ -1,4 +1,4 @@ -package com.gdschongik.gdsc.global.property.email; +package com.gdschongik.gdsc.global.property; import java.util.Map; import lombok.AllArgsConstructor; From 34ab9af52b1d5ad2e5905640fa10e65582920b42 Mon Sep 17 00:00:00 2001 From: Jaehyun Ahn <91878695+uwoobeat@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:58:56 +0900 Subject: [PATCH 05/11] =?UTF-8?q?fix:=20Gradle=20Build=20Scan=20Report?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=9C=ED=96=89=ED=95=98=EB=8F=84=EB=A1=9D=20CI?= =?UTF-8?q?=20=EC=9B=8C=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#266)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: 빌드 스캔 레포트 발행 활성화 --- .github/workflows/pull_request_gradle_build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pull_request_gradle_build.yml b/.github/workflows/pull_request_gradle_build.yml index c88599782..1fb9b286b 100644 --- a/.github/workflows/pull_request_gradle_build.yml +++ b/.github/workflows/pull_request_gradle_build.yml @@ -38,3 +38,6 @@ jobs: cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} # feature 브랜치는 캐시를 읽기 전용으로 설정 cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} add-job-summary-as-pr-comment: always + build-scan-publish: true + build-scan-terms-of-service-url: "https://gradle.com/terms-of-service" + build-scan-terms-of-service-agree: "yes" From 63d098d51548efed559b03af7ad2be063b9b81d6 Mon Sep 17 00:00:00 2001 From: Jaehyun Ahn <91878695+uwoobeat@users.noreply.github.com> Date: Wed, 6 Mar 2024 21:38:20 +0900 Subject: [PATCH 06/11] =?UTF-8?q?test:=20=ED=86=B5=ED=95=A9=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=99=98=EA=B2=BD=20=EC=84=B8=ED=8C=85=20?= =?UTF-8?q?(#268)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: transform 사용 위한 설정 업데이트 * chore: 테스트에도 롬복 사용하도록 변경 * test: 데이터베이스 클리너 구현 * test: 통합 테스트 템플릿 구현 * test: 통합 테스트 템플릿 적용 --- build.gradle | 8 +++- .../gdsc/config/TestQuerydslConfig.java | 3 +- .../application/AdminMemberServiceTest.java | 8 ++-- .../gdsc/integration/DatabaseCleaner.java | 48 +++++++++++++++++++ .../gdsc/integration/IntegrationTest.java | 19 ++++++++ 5 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/gdschongik/gdsc/integration/DatabaseCleaner.java create mode 100644 src/test/java/com/gdschongik/gdsc/integration/IntegrationTest.java diff --git a/build.gradle b/build.gradle index 21b2e716a..55883f404 100644 --- a/build.gradle +++ b/build.gradle @@ -31,12 +31,16 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' - compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + // Lombok + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' + // Spring Security implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-security' diff --git a/src/test/java/com/gdschongik/gdsc/config/TestQuerydslConfig.java b/src/test/java/com/gdschongik/gdsc/config/TestQuerydslConfig.java index 621c310c4..c8e932d30 100644 --- a/src/test/java/com/gdschongik/gdsc/config/TestQuerydslConfig.java +++ b/src/test/java/com/gdschongik/gdsc/config/TestQuerydslConfig.java @@ -1,5 +1,6 @@ package com.gdschongik.gdsc.config; +import com.querydsl.jpa.JPQLTemplates; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @@ -13,6 +14,6 @@ public class TestQuerydslConfig { @Bean public JPAQueryFactory jpaQueryFactory() { - return new JPAQueryFactory(entityManager); + return new JPAQueryFactory(JPQLTemplates.DEFAULT, entityManager); } } diff --git a/src/test/java/com/gdschongik/gdsc/domain/member/application/AdminMemberServiceTest.java b/src/test/java/com/gdschongik/gdsc/domain/member/application/AdminMemberServiceTest.java index 1a8114bf6..48eef3197 100644 --- a/src/test/java/com/gdschongik/gdsc/domain/member/application/AdminMemberServiceTest.java +++ b/src/test/java/com/gdschongik/gdsc/domain/member/application/AdminMemberServiceTest.java @@ -8,14 +8,12 @@ import com.gdschongik.gdsc.domain.member.dto.request.MemberUpdateRequest; import com.gdschongik.gdsc.global.exception.CustomException; import com.gdschongik.gdsc.global.exception.ErrorCode; +import com.gdschongik.gdsc.integration.IntegrationTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -@SpringBootTest -@ActiveProfiles("test") -class AdminMemberServiceTest { +class AdminMemberServiceTest extends IntegrationTest { + @Autowired private MemberRepository memberRepository; diff --git a/src/test/java/com/gdschongik/gdsc/integration/DatabaseCleaner.java b/src/test/java/com/gdschongik/gdsc/integration/DatabaseCleaner.java new file mode 100644 index 000000000..e6070531a --- /dev/null +++ b/src/test/java/com/gdschongik/gdsc/integration/DatabaseCleaner.java @@ -0,0 +1,48 @@ +package com.gdschongik.gdsc.integration; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.metamodel.EntityType; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; +import org.hibernate.Session; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +@Component +public class DatabaseCleaner implements InitializingBean { + + @PersistenceContext + private EntityManager em; + + private List tableNames; + + @Override + public void afterPropertiesSet() { + em.unwrap(Session.class).doWork(this::extractTableNames); + } + + private void extractTableNames(Connection conn) throws SQLException { + tableNames = em.getMetamodel().getEntities().stream() + .map(EntityType::getName) + .toList(); + } + + public void execute() { + em.unwrap(Session.class).doWork(this::cleanTables); + } + + private void cleanTables(Connection conn) throws SQLException { + Statement statement = conn.createStatement(); + statement.executeUpdate("SET REFERENTIAL_INTEGRITY FALSE"); + + for (String name : tableNames) { + statement.executeUpdate(String.format("TRUNCATE TABLE %s", name)); + statement.executeUpdate(String.format("ALTER TABLE %s ALTER COLUMN %s_id RESTART WITH 1", name, name)); + } + + statement.executeUpdate("SET REFERENTIAL_INTEGRITY TRUE"); + } +} diff --git a/src/test/java/com/gdschongik/gdsc/integration/IntegrationTest.java b/src/test/java/com/gdschongik/gdsc/integration/IntegrationTest.java new file mode 100644 index 000000000..c8634f8b6 --- /dev/null +++ b/src/test/java/com/gdschongik/gdsc/integration/IntegrationTest.java @@ -0,0 +1,19 @@ +package com.gdschongik.gdsc.integration; + +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles("test") +public abstract class IntegrationTest { + + @Autowired + protected DatabaseCleaner databaseCleaner; + + @BeforeEach + void setUp() { + databaseCleaner.execute(); + } +} From 53b0a43742ab36c29eba71ac9043eca76aa45922 Mon Sep 17 00:00:00 2001 From: Jaehyun Ahn <91878695+uwoobeat@users.noreply.github.com> Date: Wed, 6 Mar 2024 22:40:52 +0900 Subject: [PATCH 07/11] =?UTF-8?q?refactor:=20QueryDSL=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=20=EC=A1=B0=EA=B1=B4=EC=9D=84=20=EB=B3=84=EB=8F=84=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20(#273)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 쿼리 메서드 클래스 상속하도록 변경 * refactor: 메서드 위치 변경 * refactor: 학과코드 검색 리팩토링 * refactor: enum 필드 final로 변경 --- .../dao/MemberCustomRepositoryImpl.java | 86 ++----------------- .../domain/member/dao/MemberQueryMethod.java | 80 +++++++++++++++++ .../gdsc/domain/member/domain/Department.java | 18 ++-- 3 files changed, 98 insertions(+), 86 deletions(-) create mode 100644 src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberQueryMethod.java diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java index 6961c4e86..d6933d264 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java @@ -4,14 +4,10 @@ import static com.gdschongik.gdsc.domain.member.domain.RequirementStatus.*; import static com.querydsl.core.group.GroupBy.*; -import com.gdschongik.gdsc.domain.member.domain.Department; import com.gdschongik.gdsc.domain.member.domain.Member; import com.gdschongik.gdsc.domain.member.domain.MemberRole; import com.gdschongik.gdsc.domain.member.domain.RequirementStatus; import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryRequest; -import com.querydsl.core.BooleanBuilder; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.core.types.dsl.EnumPath; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.annotation.Nullable; @@ -26,7 +22,7 @@ import org.springframework.data.support.PageableExecutionUtils; @RequiredArgsConstructor -public class MemberCustomRepositoryImpl implements MemberCustomRepository { +public class MemberCustomRepositoryImpl extends MemberQueryMethod implements MemberCustomRepository { private final JPAQueryFactory queryFactory; @@ -107,15 +103,6 @@ public Map> groupByVerified(List memberIdList) { return replaceNullByEmptyList(groupByVerified); } - @Override - public List findAllByRole(MemberRole role) { - return queryFactory - .selectFrom(member) - .where(eqRole(role), isStudentIdNotNull()) - .orderBy(member.studentId.asc(), member.name.asc()) - .fetch(); - } - private Map> replaceNullByEmptyList(Map> groupByVerified) { Map> classifiedMember = new HashMap<>(); List emptyList = new ArrayList<>(); @@ -124,69 +111,12 @@ private Map> replaceNullByEmptyList(Map requirement, RequirementStatus requirementStatus) { - return requirementStatus != null ? requirement.eq(requirementStatus) : null; - } - - private BooleanExpression eqOauthId(String oauthId) { - return member.oauthId.eq(oauthId); - } - - private BooleanBuilder queryOption(MemberQueryRequest queryRequest) { - BooleanBuilder booleanBuilder = new BooleanBuilder(); - - return booleanBuilder - .and(eqStudentId(queryRequest.studentId())) - .and(eqName(queryRequest.name())) - .and(eqPhone(queryRequest.phone())) - .and(inDepartmentList(Department.getDepartmentCodes(queryRequest.department()))) - .and(eqEmail(queryRequest.email())) - .and(eqDiscordUsername(queryRequest.discordUsername())) - .and(eqNickname(queryRequest.nickname())); - } - - private BooleanExpression inDepartmentList(List departmentCodes) { - return departmentCodes != null ? member.department.in(departmentCodes) : null; - } - - private BooleanExpression eqStudentId(String studentId) { - return studentId != null ? member.studentId.containsIgnoreCase(studentId) : null; - } - - private BooleanExpression eqName(String name) { - return name != null ? member.name.containsIgnoreCase(name) : null; - } - - private BooleanExpression eqPhone(String phone) { - return phone != null ? member.phone.contains(phone.replaceAll("-", "")) : null; - } - - private BooleanExpression eqEmail(String email) { - return email != null ? member.email.containsIgnoreCase(email) : null; - } - - private BooleanExpression eqDiscordUsername(String discordUsername) { - return discordUsername != null ? member.discordUsername.containsIgnoreCase(discordUsername) : null; - } - - private BooleanExpression eqNickname(String nickname) { - return nickname != null ? member.nickname.containsIgnoreCase(nickname) : null; - } - - private BooleanExpression isStudentIdNotNull() { - return member.studentId.isNotNull(); + @Override + public List findAllByRole(MemberRole role) { + return queryFactory + .selectFrom(member) + .where(eqRole(role), isStudentIdNotNull()) + .orderBy(member.studentId.asc(), member.name.asc()) + .fetch(); } } diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberQueryMethod.java b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberQueryMethod.java new file mode 100644 index 000000000..19c716360 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberQueryMethod.java @@ -0,0 +1,80 @@ +package com.gdschongik.gdsc.domain.member.dao; + +import static com.gdschongik.gdsc.domain.member.domain.QMember.*; +import static com.gdschongik.gdsc.domain.member.domain.RequirementStatus.*; + +import com.gdschongik.gdsc.domain.member.domain.Department; +import com.gdschongik.gdsc.domain.member.domain.MemberRole; +import com.gdschongik.gdsc.domain.member.domain.RequirementStatus; +import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryRequest; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.EnumPath; +import java.util.List; + +public class MemberQueryMethod { + + protected BooleanExpression eqRole(MemberRole role) { + return role != null ? member.role.eq(role) : null; + } + + protected BooleanExpression eqStudentId(String studentId) { + return studentId != null ? member.studentId.containsIgnoreCase(studentId) : null; + } + + protected BooleanExpression eqName(String name) { + return name != null ? member.name.containsIgnoreCase(name) : null; + } + + protected BooleanExpression eqPhone(String phone) { + return phone != null ? member.phone.contains(phone.replaceAll("-", "")) : null; + } + + protected BooleanExpression eqEmail(String email) { + return email != null ? member.email.containsIgnoreCase(email) : null; + } + + protected BooleanExpression eqDiscordUsername(String discordUsername) { + return discordUsername != null ? member.discordUsername.containsIgnoreCase(discordUsername) : null; + } + + protected BooleanExpression eqNickname(String nickname) { + return nickname != null ? member.nickname.containsIgnoreCase(nickname) : null; + } + + protected BooleanExpression eqOauthId(String oauthId) { + return member.oauthId.eq(oauthId); + } + + protected BooleanExpression eqRequirementStatus( + EnumPath requirement, RequirementStatus requirementStatus) { + return requirementStatus != null ? requirement.eq(requirementStatus) : null; + } + + protected BooleanExpression inDepartmentList(List departmentCodes) { + return departmentCodes != null ? member.department.in(departmentCodes) : null; + } + + protected BooleanExpression isStudentIdNotNull() { + return member.studentId.isNotNull(); + } + + protected BooleanBuilder requirementVerified() { + return new BooleanBuilder() + .and(eqRequirementStatus(member.requirement.discordStatus, VERIFIED)) + .and(eqRequirementStatus(member.requirement.univStatus, VERIFIED)) + .and(eqRequirementStatus(member.requirement.paymentStatus, VERIFIED)) + .and(eqRequirementStatus(member.requirement.bevyStatus, VERIFIED)); + } + + protected BooleanBuilder queryOption(MemberQueryRequest queryRequest) { + return new BooleanBuilder() + .and(eqStudentId(queryRequest.studentId())) + .and(eqName(queryRequest.name())) + .and(eqPhone(queryRequest.phone())) + .and(inDepartmentList(Department.searchDepartments(queryRequest.department()))) + .and(eqEmail(queryRequest.email())) + .and(eqDiscordUsername(queryRequest.discordUsername())) + .and(eqNickname(queryRequest.nickname())); + } +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/domain/Department.java b/src/main/java/com/gdschongik/gdsc/domain/member/domain/Department.java index 184a77168..650f353ea 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/domain/Department.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/domain/Department.java @@ -1,8 +1,8 @@ package com.gdschongik.gdsc.domain.member.domain; import java.util.Arrays; +import java.util.Collections; import java.util.List; -import java.util.Optional; import lombok.AllArgsConstructor; import lombok.Getter; @@ -87,13 +87,15 @@ public enum Department { D076("역사교육과"), D077("영어교육과"); - private String departmentName; + private final String departmentName; - public static List getDepartmentCodes(String keyword) { - return Optional.ofNullable(keyword) - .map(s -> Arrays.stream(Department.values()) - .filter(department -> department.getDepartmentName().contains(s)) - .toList()) - .orElse(null); + public static List searchDepartments(String keyword) { + if (keyword == null) { + return Collections.emptyList(); + } + + return Arrays.stream(Department.values()) + .filter(department -> department.getDepartmentName().contains(keyword)) + .toList(); } } From 2319f1fa2c279c31917986d7d92691754d54ffc9 Mon Sep 17 00:00:00 2001 From: Jaehyun Ahn <91878695+uwoobeat@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:16:24 +0900 Subject: [PATCH 08/11] =?UTF-8?q?refactor:=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9D=B4=EB=A6=84=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20=EB=B0=8F=20=EC=97=B0=EA=B4=80=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80=20(#274)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 쿼리 옵션 이름 변경 * refactor: 승인가능 쿼리조건 이름 변경 * docs: 도메인 로직에 쿼리 조건 메서드 레퍼런스 추가 * refactor: 쿼리옵션 DTO 이름 수정 --- .../member/api/AdminMemberController.java | 22 +++++++++--------- .../application/AdminMemberService.java | 22 +++++++++--------- .../member/dao/MemberCustomRepository.java | 8 +++---- .../dao/MemberCustomRepositoryImpl.java | 23 +++++++++---------- .../domain/member/dao/MemberQueryMethod.java | 20 ++++++++-------- .../gdsc/domain/member/domain/Member.java | 5 ++++ ...eryRequest.java => MemberQueryOption.java} | 2 +- 7 files changed, 53 insertions(+), 49 deletions(-) rename src/main/java/com/gdschongik/gdsc/domain/member/dto/request/{MemberQueryRequest.java => MemberQueryOption.java} (95%) diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/api/AdminMemberController.java b/src/main/java/com/gdschongik/gdsc/domain/member/api/AdminMemberController.java index 139e6f896..28b747dd5 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/api/AdminMemberController.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/api/AdminMemberController.java @@ -4,7 +4,7 @@ import com.gdschongik.gdsc.domain.member.domain.RequirementStatus; import com.gdschongik.gdsc.domain.member.dto.request.MemberGrantRequest; import com.gdschongik.gdsc.domain.member.dto.request.MemberPaymentRequest; -import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryRequest; +import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryOption; import com.gdschongik.gdsc.domain.member.dto.request.MemberUpdateRequest; import com.gdschongik.gdsc.domain.member.dto.response.AdminMemberResponse; import com.gdschongik.gdsc.domain.member.dto.response.MemberGrantResponse; @@ -37,8 +37,8 @@ public class AdminMemberController { @Operation(summary = "전체 회원 목록 조회", description = "전체 회원 목록을 조회합니다.") @GetMapping - public ResponseEntity> getMembers(MemberQueryRequest queryRequest, Pageable pageable) { - Page response = adminMemberService.findAll(queryRequest, pageable); + public ResponseEntity> getMembers(MemberQueryOption queryOption, Pageable pageable) { + Page response = adminMemberService.findAll(queryOption, pageable); return ResponseEntity.ok().body(response); } @@ -52,8 +52,8 @@ public ResponseEntity withdrawMember(@PathVariable Long memberId) { @Operation(summary = "대기중인 회원 목록 조회", description = "대기중인 회원 목록을 조회합니다.") @GetMapping("/pending") public ResponseEntity> getPendingMembers( - MemberQueryRequest queryRequest, Pageable pageable) { - Page response = adminMemberService.findAllPendingMembers(queryRequest, pageable); + MemberQueryOption queryOption, Pageable pageable) { + Page response = adminMemberService.findAllPendingMembers(queryOption, pageable); return ResponseEntity.ok().body(response); } @@ -75,19 +75,19 @@ public ResponseEntity grantMember(@Valid @RequestBody Membe @Operation(summary = "승인 가능 회원 전체 조회", description = "승인 가능한 회원 전체를 조회합니다.") @GetMapping("/grantable") public ResponseEntity> getGrantableMembers( - MemberQueryRequest queryRequest, Pageable pageable) { - Page response = adminMemberService.getGrantableMembers(queryRequest, pageable); + MemberQueryOption queryOption, Pageable pageable) { + Page response = adminMemberService.getGrantableMembers(queryOption, pageable); return ResponseEntity.ok().body(response); } @Operation(summary = "회비 납부 상태에 따른 회원 전체 조회", description = "회비 납부 상태에 따라 회원 목록을 조회합니다.") @GetMapping("/payment") public ResponseEntity> getMembersByPaymentStatus( - MemberQueryRequest queryRequest, + MemberQueryOption queryOption, @RequestParam(name = "status", required = false) RequirementStatus paymentStatus, Pageable pageable) { Page response = - adminMemberService.getMembersByPaymentStatus(queryRequest, paymentStatus, pageable); + adminMemberService.getMembersByPaymentStatus(queryOption, paymentStatus, pageable); return ResponseEntity.ok().body(response); } @@ -102,8 +102,8 @@ public ResponseEntity updatePayment( @Operation(summary = "승인된 회원 전체 조회", description = "승인된 회원 전체를 조회합니다.") @GetMapping("/granted") public ResponseEntity> getGrantedMembers( - MemberQueryRequest queryRequest, Pageable pageable) { - Page response = adminMemberService.findAllGrantedMembers(queryRequest, pageable); + MemberQueryOption queryOption, Pageable pageable) { + Page response = adminMemberService.findAllGrantedMembers(queryOption, pageable); return ResponseEntity.ok().body(response); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/application/AdminMemberService.java b/src/main/java/com/gdschongik/gdsc/domain/member/application/AdminMemberService.java index 76f55ac08..243e6917f 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/application/AdminMemberService.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/application/AdminMemberService.java @@ -8,7 +8,7 @@ import com.gdschongik.gdsc.domain.member.domain.RequirementStatus; import com.gdschongik.gdsc.domain.member.dto.request.MemberGrantRequest; import com.gdschongik.gdsc.domain.member.dto.request.MemberPaymentRequest; -import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryRequest; +import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryOption; import com.gdschongik.gdsc.domain.member.dto.request.MemberUpdateRequest; import com.gdschongik.gdsc.domain.member.dto.response.AdminMemberResponse; import com.gdschongik.gdsc.domain.member.dto.response.MemberGrantResponse; @@ -32,8 +32,8 @@ public class AdminMemberService { private final MemberRepository memberRepository; private final ExcelUtil excelUtil; - public Page findAll(MemberQueryRequest queryRequest, Pageable pageable) { - Page members = memberRepository.findAllByRole(queryRequest, pageable, null); + public Page findAll(MemberQueryOption queryOption, Pageable pageable) { + Page members = memberRepository.findAllByRole(queryOption, pageable, null); return members.map(AdminMemberResponse::from); } @@ -57,8 +57,8 @@ public void updateMember(Long memberId, MemberUpdateRequest request) { request.nickname()); } - public Page findAllPendingMembers(MemberQueryRequest queryRequest, Pageable pageable) { - Page members = memberRepository.findAllByRole(queryRequest, pageable, GUEST); + public Page findAllPendingMembers(MemberQueryOption queryOption, Pageable pageable) { + Page members = memberRepository.findAllByRole(queryOption, pageable, GUEST); return members.map(AdminMemberResponse::from); } @@ -70,14 +70,14 @@ public MemberGrantResponse grantMember(MemberGrantRequest request) { return MemberGrantResponse.from(classifiedMember); } - public Page getGrantableMembers(MemberQueryRequest queryRequest, Pageable pageable) { - Page members = memberRepository.findAllGrantable(queryRequest, pageable); + public Page getGrantableMembers(MemberQueryOption queryOption, Pageable pageable) { + Page members = memberRepository.findAllGrantable(queryOption, pageable); return members.map(AdminMemberResponse::from); } public Page getMembersByPaymentStatus( - MemberQueryRequest queryRequest, RequirementStatus paymentStatus, Pageable pageable) { - Page members = memberRepository.findAllByPaymentStatus(queryRequest, paymentStatus, pageable); + MemberQueryOption queryOption, RequirementStatus paymentStatus, Pageable pageable) { + Page members = memberRepository.findAllByPaymentStatus(queryOption, paymentStatus, pageable); return members.map(AdminMemberResponse::from); } @@ -87,8 +87,8 @@ public void updatePaymentStatus(Long memberId, MemberPaymentRequest request) { member.updatePaymentStatus(request.status()); } - public Page findAllGrantedMembers(MemberQueryRequest queryRequest, Pageable pageable) { - Page members = memberRepository.findAllByRole(queryRequest, pageable, USER); + public Page findAllGrantedMembers(MemberQueryOption queryOption, Pageable pageable) { + Page members = memberRepository.findAllByRole(queryOption, pageable, USER); return members.map(AdminMemberResponse::from); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepository.java b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepository.java index fc7e5b3ea..3d38a932e 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepository.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepository.java @@ -3,7 +3,7 @@ import com.gdschongik.gdsc.domain.member.domain.Member; import com.gdschongik.gdsc.domain.member.domain.MemberRole; import com.gdschongik.gdsc.domain.member.domain.RequirementStatus; -import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryRequest; +import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryOption; import jakarta.annotation.Nullable; import java.util.List; import java.util.Map; @@ -14,12 +14,12 @@ public interface MemberCustomRepository { Optional findNormalByOauthId(String oauthId); - Page findAllGrantable(MemberQueryRequest queryRequest, Pageable pageable); + Page findAllGrantable(MemberQueryOption queryOption, Pageable pageable); - Page findAllByRole(MemberQueryRequest queryRequest, Pageable pageable, @Nullable MemberRole role); + Page findAllByRole(MemberQueryOption queryOption, Pageable pageable, @Nullable MemberRole role); Page findAllByPaymentStatus( - MemberQueryRequest queryRequest, RequirementStatus paymentStatus, Pageable pageable); + MemberQueryOption queryOption, RequirementStatus paymentStatus, Pageable pageable); Map> groupByVerified(List memberIdList); diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java index d6933d264..4b52752af 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java @@ -1,13 +1,12 @@ package com.gdschongik.gdsc.domain.member.dao; import static com.gdschongik.gdsc.domain.member.domain.QMember.*; -import static com.gdschongik.gdsc.domain.member.domain.RequirementStatus.*; import static com.querydsl.core.group.GroupBy.*; import com.gdschongik.gdsc.domain.member.domain.Member; import com.gdschongik.gdsc.domain.member.domain.MemberRole; import com.gdschongik.gdsc.domain.member.domain.RequirementStatus; -import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryRequest; +import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryOption; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.annotation.Nullable; @@ -33,10 +32,10 @@ public Optional findNormalByOauthId(String oauthId) { } @Override - public Page findAllGrantable(MemberQueryRequest queryRequest, Pageable pageable) { + public Page findAllGrantable(MemberQueryOption queryOption, Pageable pageable) { List fetch = queryFactory .selectFrom(member) - .where(queryOption(queryRequest), eqRole(MemberRole.GUEST), requirementVerified()) + .where(matchesQueryOption(queryOption), eqRole(MemberRole.GUEST), isGrantAvailable()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .orderBy(member.createdAt.desc()) @@ -45,16 +44,16 @@ public Page findAllGrantable(MemberQueryRequest queryRequest, Pageable p JPAQuery countQuery = queryFactory .select(member.count()) .from(member) - .where(queryOption(queryRequest), eqRole(MemberRole.GUEST), requirementVerified()); + .where(matchesQueryOption(queryOption), eqRole(MemberRole.GUEST), isGrantAvailable()); return PageableExecutionUtils.getPage(fetch, pageable, countQuery::fetchOne); } @Override - public Page findAllByRole(MemberQueryRequest queryRequest, Pageable pageable, @Nullable MemberRole role) { + public Page findAllByRole(MemberQueryOption queryOption, Pageable pageable, @Nullable MemberRole role) { List fetch = queryFactory .selectFrom(member) - .where(queryOption(queryRequest), eqRole(role), isStudentIdNotNull()) + .where(matchesQueryOption(queryOption), eqRole(role), isStudentIdNotNull()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .orderBy(member.createdAt.desc()) @@ -63,18 +62,18 @@ public Page findAllByRole(MemberQueryRequest queryRequest, Pageable page JPAQuery countQuery = queryFactory .select(member.count()) .from(member) - .where(queryOption(queryRequest), eqRole(role), isStudentIdNotNull()); + .where(matchesQueryOption(queryOption), eqRole(role), isStudentIdNotNull()); return PageableExecutionUtils.getPage(fetch, pageable, countQuery::fetchOne); } @Override public Page findAllByPaymentStatus( - MemberQueryRequest queryRequest, RequirementStatus paymentStatus, Pageable pageable) { + MemberQueryOption queryOption, RequirementStatus paymentStatus, Pageable pageable) { List fetch = queryFactory .selectFrom(member) .where( - queryOption(queryRequest), + matchesQueryOption(queryOption), eqRequirementStatus(member.requirement.paymentStatus, paymentStatus), isStudentIdNotNull()) .offset(pageable.getOffset()) @@ -86,7 +85,7 @@ public Page findAllByPaymentStatus( .select(member.count()) .from(member) .where( - queryOption(queryRequest), + matchesQueryOption(queryOption), eqRequirementStatus(member.requirement.paymentStatus, paymentStatus), isStudentIdNotNull()); @@ -98,7 +97,7 @@ public Map> groupByVerified(List memberIdList) { Map> groupByVerified = queryFactory .selectFrom(member) .where(member.id.in(memberIdList)) - .transform(groupBy(requirementVerified()).as(list(member))); + .transform(groupBy(isGrantAvailable()).as(list(member))); return replaceNullByEmptyList(groupByVerified); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberQueryMethod.java b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberQueryMethod.java index 19c716360..172eb661d 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberQueryMethod.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberQueryMethod.java @@ -6,7 +6,7 @@ import com.gdschongik.gdsc.domain.member.domain.Department; import com.gdschongik.gdsc.domain.member.domain.MemberRole; import com.gdschongik.gdsc.domain.member.domain.RequirementStatus; -import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryRequest; +import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryOption; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.EnumPath; @@ -59,7 +59,7 @@ protected BooleanExpression isStudentIdNotNull() { return member.studentId.isNotNull(); } - protected BooleanBuilder requirementVerified() { + protected BooleanBuilder isGrantAvailable() { return new BooleanBuilder() .and(eqRequirementStatus(member.requirement.discordStatus, VERIFIED)) .and(eqRequirementStatus(member.requirement.univStatus, VERIFIED)) @@ -67,14 +67,14 @@ protected BooleanBuilder requirementVerified() { .and(eqRequirementStatus(member.requirement.bevyStatus, VERIFIED)); } - protected BooleanBuilder queryOption(MemberQueryRequest queryRequest) { + protected BooleanBuilder matchesQueryOption(MemberQueryOption queryOption) { return new BooleanBuilder() - .and(eqStudentId(queryRequest.studentId())) - .and(eqName(queryRequest.name())) - .and(eqPhone(queryRequest.phone())) - .and(inDepartmentList(Department.searchDepartments(queryRequest.department()))) - .and(eqEmail(queryRequest.email())) - .and(eqDiscordUsername(queryRequest.discordUsername())) - .and(eqNickname(queryRequest.nickname())); + .and(eqStudentId(queryOption.studentId())) + .and(eqName(queryOption.name())) + .and(eqPhone(queryOption.phone())) + .and(inDepartmentList(Department.searchDepartments(queryOption.department()))) + .and(eqEmail(queryOption.email())) + .and(eqDiscordUsername(queryOption.discordUsername())) + .and(eqNickname(queryOption.nickname())); } } diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java b/src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java index 1a0424525..9805232db 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java @@ -240,6 +240,11 @@ public boolean isGranted() { return role.equals(USER) || role.equals(MemberRole.ADMIN); } + /** + * 회원 승인 가능 여부를 반환합니다. + * + * @see com.gdschongik.gdsc.domain.member.dao.MemberQueryMethod#isGrantAvailable() + */ public boolean isGrantAvailable() { try { validateGrantAvailable(); diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dto/request/MemberQueryRequest.java b/src/main/java/com/gdschongik/gdsc/domain/member/dto/request/MemberQueryOption.java similarity index 95% rename from src/main/java/com/gdschongik/gdsc/domain/member/dto/request/MemberQueryRequest.java rename to src/main/java/com/gdschongik/gdsc/domain/member/dto/request/MemberQueryOption.java index 56ac95fa8..26daf58e2 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/dto/request/MemberQueryRequest.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dto/request/MemberQueryOption.java @@ -4,7 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; -public record MemberQueryRequest( +public record MemberQueryOption( @Schema(description = "학번", pattern = STUDENT_ID) String studentId, @Schema(description = "이름") String name, @Schema(description = "전화번호", pattern = PHONE_WITHOUT_HYPHEN) String phone, From a67923c59f33d891bc8b2c3eac4c18e5ed853908 Mon Sep 17 00:00:00 2001 From: Jaehyun Ahn <91878695+uwoobeat@users.noreply.github.com> Date: Thu, 7 Mar 2024 01:06:24 +0900 Subject: [PATCH 09/11] =?UTF-8?q?fix:=20CI=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=EC=97=90=EC=84=9C=20Gradle=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=EA=B0=80=20=EC=88=98=ED=96=89=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20(#276)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: build 작업을 check 작업으로 수정 --- .github/workflows/pull_request_gradle_build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request_gradle_build.yml b/.github/workflows/pull_request_gradle_build.yml index 1fb9b286b..dabd4105b 100644 --- a/.github/workflows/pull_request_gradle_build.yml +++ b/.github/workflows/pull_request_gradle_build.yml @@ -33,7 +33,7 @@ jobs: uses: gradle/actions/setup-gradle@v3 with: arguments: | - build + check --configuration-cache cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} # feature 브랜치는 캐시를 읽기 전용으로 설정 cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} From 24a51ddc79d52d9f84eebd6c554f8a676e713f5c Mon Sep 17 00:00:00 2001 From: Cho Sangwook <82208159+Sangwook02@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:27:12 +0900 Subject: [PATCH 10/11] =?UTF-8?q?refactor:=20=EB=B0=9C=EC=83=9D=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=EC=84=B1=20=EC=9E=88=EB=8A=94=20NPE=EB=A5=BC?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0=20(#170)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 발생 가능성 있는 NPE를 제거 * refactor: 가입 신청서를 작성하지 않은 경우 예외 발생하도록 수정 * rename: 가입 신청서 작성을 apply로 정의 * refactor: 가입 신청서 작성 여부 확인 로직을 도메인으로 이동 --- .../member/application/OnboardingMemberService.java | 8 ++++++-- .../com/gdschongik/gdsc/domain/member/domain/Member.java | 7 +++++++ .../com/gdschongik/gdsc/global/exception/ErrorCode.java | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/application/OnboardingMemberService.java b/src/main/java/com/gdschongik/gdsc/domain/member/application/OnboardingMemberService.java index 85f8da311..e48be9528 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/application/OnboardingMemberService.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/application/OnboardingMemberService.java @@ -1,5 +1,7 @@ package com.gdschongik.gdsc.domain.member.application; +import static com.gdschongik.gdsc.global.exception.ErrorCode.*; + import com.gdschongik.gdsc.domain.member.dao.MemberRepository; import com.gdschongik.gdsc.domain.member.domain.Member; import com.gdschongik.gdsc.domain.member.dto.request.MemberSignupRequest; @@ -7,7 +9,6 @@ import com.gdschongik.gdsc.domain.member.dto.response.MemberInfoResponse; import com.gdschongik.gdsc.domain.member.dto.response.MemberUnivStatusResponse; import com.gdschongik.gdsc.global.exception.CustomException; -import com.gdschongik.gdsc.global.exception.ErrorCode; import com.gdschongik.gdsc.global.util.MemberUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -38,12 +39,15 @@ public void updateMember(OnboardingMemberUpdateRequest request) { private void validateDiscordUsernameDuplicate(Member member) { if (memberRepository.existsByDiscordUsername(member.getDiscordUsername())) { - throw new CustomException(ErrorCode.MEMBER_DISCORD_USERNAME_DUPLICATE); + throw new CustomException(MEMBER_DISCORD_USERNAME_DUPLICATE); } } public MemberInfoResponse getMemberInfo() { Member currentMember = memberUtil.getCurrentMember(); + if (!currentMember.isApplied()) { + throw new CustomException(MEMBER_NOT_APPLIED); + } return MemberInfoResponse.of(currentMember); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java b/src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java index 9805232db..e6607c02f 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java @@ -254,6 +254,13 @@ public boolean isGrantAvailable() { } } + /** + * 가입 신청서 제출 여부를 반환합니다. + */ + public boolean isApplied() { + return studentId != null; + } + // 기타 로직 public void updateLastLoginAt() { diff --git a/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java b/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java index ee7e8ce32..6d2d73c3c 100644 --- a/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java +++ b/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java @@ -33,6 +33,7 @@ public enum ErrorCode { MEMBER_ALREADY_VERIFIED(HttpStatus.CONFLICT, "이미 인증된 상태입니다."), MEMBER_DISCORD_USERNAME_DUPLICATE(HttpStatus.CONFLICT, "이미 등록된 디스코드 유저네임입니다."), MEMBER_NICKNAME_DUPLICATE(HttpStatus.CONFLICT, "이미 사용중인 닉네임입니다."), + MEMBER_NOT_APPLIED(HttpStatus.CONFLICT, "가입신청서를 제출하지 않은 회원입니다."), // Requirement UNIV_NOT_VERIFIED(HttpStatus.CONFLICT, "재학생 인증이 완료되지 않았습니다."), From 1f39f549c20845c4daa7b08a6aad64abe9f527d0 Mon Sep 17 00:00:00 2001 From: Cho Sangwook <82208159+Sangwook02@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:10:15 +0900 Subject: [PATCH 11/11] =?UTF-8?q?feat:=202=EC=B0=A8=20=EB=AA=A8=EC=A7=91?= =?UTF-8?q?=20=EA=B8=B0=EA=B0=84=20=EB=A7=88=EA=B0=90=20=EC=8B=9C=20Landin?= =?UTF-8?q?gStatus=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80=20(#278)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 2차 모집 기간 마감 시 LandingStatus 조건 추가 * refactor: 변경된 가입 정책에 따라 수정 * refactor: 1시 이후 guest를 마감 페이지로 랜딩 --- .../gdsc/global/security/LandingStatus.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/gdschongik/gdsc/global/security/LandingStatus.java b/src/main/java/com/gdschongik/gdsc/global/security/LandingStatus.java index 3abc80e72..98901a2c1 100644 --- a/src/main/java/com/gdschongik/gdsc/global/security/LandingStatus.java +++ b/src/main/java/com/gdschongik/gdsc/global/security/LandingStatus.java @@ -1,11 +1,13 @@ package com.gdschongik.gdsc.global.security; import com.gdschongik.gdsc.domain.member.domain.Member; +import com.gdschongik.gdsc.domain.member.domain.MemberRole; import java.time.LocalDate; import java.time.LocalDateTime; public enum LandingStatus { ONBOARDING_NOT_OPENED, // 대기 페이지로 랜딩 + ONBOARDING_CLOSED, // 모집 기간 마감 TO_STUDENT_AUTHENTICATION, // 재학생 인증 페이지로 랜딩 TO_REGISTRATION, // 가입신청 페이지로 랜딩 TO_DASHBOARD, // 대시보드로 랜딩 @@ -19,6 +21,17 @@ public static LandingStatus of(Member member) { return ONBOARDING_NOT_OPENED; } + // 2차 모집기간 종료일 12시 30분 이후, 신청서 미제출 상태면 마감 페이지로 랜딩 + if (LocalDateTime.now().isAfter(Constants.SECOND_RECRUITMENT_END_DATE.atTime(0, 30)) && !member.isApplied()) { + return ONBOARDING_CLOSED; + } + + // 2차 모집기간 종료일 1시 이후, Guest를 마감 페이지로 랜딩. + if (LocalDateTime.now().isAfter(Constants.SECOND_RECRUITMENT_END_DATE.atTime(1, 0)) + && member.getRole().equals(MemberRole.GUEST)) { + return ONBOARDING_CLOSED; + } + // 아직 재학생 인증을 하지 않았다면 재학생 인증 페이지로 랜딩 if (!member.getRequirement().isUnivVerified()) { return TO_STUDENT_AUTHENTICATION; @@ -26,7 +39,7 @@ public static LandingStatus of(Member member) { // 재학생 인증은 했지만 가입신청을 하지 않았다면 가입신청 페이지로 랜딩 // 가입신청 여부는 학번 존재여부로 판단 - if (member.getStudentId() == null) { + if (!member.isApplied()) { return TO_REGISTRATION; } @@ -37,5 +50,6 @@ public static LandingStatus of(Member member) { private static class Constants { private static final LocalDate FIRST_RECRUITMENT_END_DATE = LocalDate.of(2024, 3, 2); private static final LocalDate SECOND_RECRUITMENT_START_DATE = LocalDate.of(2024, 3, 4); + private static final LocalDate SECOND_RECRUITMENT_END_DATE = LocalDate.of(2024, 3, 9); } }