Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…into refactor/470-email-verification-modify
  • Loading branch information
seulgi99 committed Jul 23, 2024
2 parents a6249a6 + e45ea44 commit c699d39
Show file tree
Hide file tree
Showing 40 changed files with 595 additions and 134 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import org.springframework.data.support.PageableExecutionUtils;

@RequiredArgsConstructor
public class IssuedCouponCustomRepositoryImpl extends IssuedCouponQueryMethod implements IssuedCouponCustomRepository {
public class IssuedCouponCustomRepositoryImpl implements IssuedCouponCustomRepository, IssuedCouponQueryMethod {

private final JPAQueryFactory queryFactory;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,39 @@
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.BooleanExpression;

public class IssuedCouponQueryMethod {
public interface IssuedCouponQueryMethod {

protected BooleanExpression eqStudentId(String studentId) {
default BooleanExpression eqStudentId(String studentId) {
return studentId != null ? issuedCoupon.member.studentId.containsIgnoreCase(studentId) : null;
}

protected BooleanExpression eqMemberName(String memberName) {
default BooleanExpression eqMemberName(String memberName) {
return memberName != null ? issuedCoupon.coupon.name.containsIgnoreCase(memberName) : null;
}

protected BooleanExpression eqPhone(String phone) {
default BooleanExpression eqPhone(String phone) {
return phone != null ? issuedCoupon.member.phone.contains(phone.replaceAll("-", "")) : null;
}

protected BooleanExpression eqCouponName(String couponName) {
default BooleanExpression eqCouponName(String couponName) {
return couponName != null ? issuedCoupon.coupon.name.containsIgnoreCase(couponName) : null;
}

protected BooleanExpression hasUsed(Boolean hasUsed) {
default BooleanExpression hasUsed(Boolean hasUsed) {
if (hasUsed == null) {
return null;
}
return hasUsed ? issuedCoupon.usedAt.isNotNull() : issuedCoupon.usedAt.isNull();
}

protected BooleanExpression hasRevoked(Boolean hasRevoked) {
default BooleanExpression hasRevoked(Boolean hasRevoked) {
if (hasRevoked == null) {
return null;
}
return hasRevoked ? issuedCoupon.hasRevoked.isTrue() : issuedCoupon.hasRevoked.isFalse();
}

protected BooleanBuilder matchesQueryOption(IssuedCouponQueryOption queryOption) {
default BooleanBuilder matchesQueryOption(IssuedCouponQueryOption queryOption) {
return new BooleanBuilder()
.and(eqStudentId(queryOption.studentId()))
.and(eqMemberName(queryOption.memberName()))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package com.gdschongik.gdsc.domain.coupon.dto.request;

import static com.gdschongik.gdsc.global.common.constant.RegexConstant.PHONE_WITHOUT_HYPHEN;
import static com.gdschongik.gdsc.global.common.constant.RegexConstant.STUDENT_ID;

import io.swagger.v3.oas.annotations.media.Schema;

public record IssuedCouponQueryOption(
@Schema(description = "학번", pattern = STUDENT_ID) String studentId,
@Schema(description = "학번") String studentId,
@Schema(description = "이름") String memberName,
@Schema(description = "전화번호", pattern = PHONE_WITHOUT_HYPHEN) String phone,
@Schema(description = "전화번호") String phone,
@Schema(description = "쿠폰 이름") String couponName,
@Schema(description = "쿠폰 사용 여부") Boolean hasUsed,
@Schema(description = "쿠폰 회수 여부") Boolean hasRevoked) {}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import static com.gdschongik.gdsc.global.exception.ErrorCode.*;

import com.gdschongik.gdsc.domain.common.model.RequirementStatus;
import com.gdschongik.gdsc.domain.discord.domain.DiscordValidator;
import com.gdschongik.gdsc.domain.member.dao.MemberRepository;
import com.gdschongik.gdsc.domain.member.domain.Member;
import com.gdschongik.gdsc.domain.member.domain.MemberRole;
import com.gdschongik.gdsc.global.exception.CustomException;
import com.gdschongik.gdsc.global.util.DiscordUtil;
import java.util.List;
Expand All @@ -19,6 +19,7 @@ public class CommonDiscordService {

private final MemberRepository memberRepository;
private final DiscordUtil discordUtil;
private final DiscordValidator discordValidator;

public String getNicknameByDiscordUsername(String discordUsername) {
return memberRepository
Expand All @@ -28,7 +29,13 @@ public String getNicknameByDiscordUsername(String discordUsername) {
}

@Transactional
public void batchDiscordId(RequirementStatus discordStatus) {
public void batchDiscordId(String currentDiscordUsername, RequirementStatus discordStatus) {
Member currentMember = memberRepository
.findByDiscordUsername(currentDiscordUsername)
.orElseThrow(() -> new CustomException(MEMBER_NOT_FOUND));

discordValidator.validateAdminPermission(currentMember);

List<Member> discordSatisfiedMembers = memberRepository.findAllByDiscordStatus(discordStatus);

discordSatisfiedMembers.forEach(member -> {
Expand All @@ -37,14 +44,4 @@ public void batchDiscordId(RequirementStatus discordStatus) {
member.updateDiscordId(discordId);
});
}

public void checkPermissionForCommand(String discordUsername) {
Member member = memberRepository
.findByDiscordUsername(discordUsername)
.orElseThrow(() -> new CustomException(MEMBER_NOT_FOUND));

if (!member.getRole().equals(MemberRole.ADMIN)) {
throw new CustomException(INVALID_ROLE);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ public void delegate(GenericEvent genericEvent) {
event.deferReply(true).setContent(DEFER_MESSAGE_BATCH_DISCORD_ID).queue();

String discordUsername = event.getUser().getName();
commonDiscordService.checkPermissionForCommand(discordUsername);
commonDiscordService.batchDiscordId(SATISFIED);
commonDiscordService.batchDiscordId(discordUsername, SATISFIED);

event.getHook()
.sendMessage(REPLY_MESSAGE_BATCH_DISCORD_ID)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static com.gdschongik.gdsc.global.exception.ErrorCode.*;

import com.gdschongik.gdsc.domain.member.domain.Member;
import com.gdschongik.gdsc.domain.member.domain.MemberRole;
import com.gdschongik.gdsc.global.annotation.DomainService;
import com.gdschongik.gdsc.global.exception.CustomException;

Expand All @@ -28,4 +30,10 @@ public void validateVerifyDiscordCode(
throw new CustomException(MEMBER_NICKNAME_DUPLICATE);
}
}

public void validateAdminPermission(Member currentMember) {
if (!currentMember.getRole().equals(MemberRole.ADMIN)) {
throw new CustomException(INVALID_ROLE);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gdschongik.gdsc.domain.member.api;

import com.gdschongik.gdsc.domain.member.application.AdminMemberService;
import com.gdschongik.gdsc.domain.member.application.OnboardingMemberService;
import com.gdschongik.gdsc.domain.member.dto.request.MemberTokenRequest;
import com.gdschongik.gdsc.domain.member.dto.response.MemberTokenResponse;
Expand All @@ -8,10 +9,7 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

@Tag(name = "Test Member", description = "회원 테스트용 API입니다. dev 환경에서만 사용 가능합니다")
@RestController
Expand All @@ -20,11 +18,19 @@
public class TestMemberController {

private final OnboardingMemberService onboardingMemberService;
private final AdminMemberService adminMemberService;

@Operation(summary = "임시 토큰 생성", description = "테스트용 API입니다. oauth_id를 입력받아 해당하는 유저의 토큰을 생성합니다.")
@PostMapping("/token")
public ResponseEntity<MemberTokenResponse> createTemporaryToken(@Valid @RequestBody MemberTokenRequest request) {
MemberTokenResponse response = onboardingMemberService.createTemporaryToken(request);
return ResponseEntity.ok().body(response);
}

@Operation(summary = "게스트로 강등", description = "테스트용 API입니다. 현재 멤버 역할을 게스트로 강등시키기 위해 사용합니다.")
@PatchMapping("/demotion")
public ResponseEntity<Void> demoteToGuest() {
adminMemberService.demoteToGuestAndRegularRequirementToPending();
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@
import com.gdschongik.gdsc.domain.member.dao.MemberRepository;
import com.gdschongik.gdsc.domain.member.domain.Member;
import com.gdschongik.gdsc.domain.member.domain.MemberRole;
import com.gdschongik.gdsc.domain.member.domain.MemberValidator;
import com.gdschongik.gdsc.domain.member.dto.request.MemberDemoteRequest;
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.recruitment.application.AdminRecruitmentService;
import com.gdschongik.gdsc.domain.membership.application.MembershipService;
import com.gdschongik.gdsc.domain.recruitment.dao.RecruitmentRoundRepository;
import com.gdschongik.gdsc.domain.recruitment.domain.RecruitmentRound;
import com.gdschongik.gdsc.global.exception.CustomException;
import com.gdschongik.gdsc.global.exception.ErrorCode;
import com.gdschongik.gdsc.global.util.EnvironmentUtil;
import com.gdschongik.gdsc.global.util.ExcelUtil;
import com.gdschongik.gdsc.global.util.MemberUtil;
import java.io.IOException;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand All @@ -30,7 +35,11 @@ public class AdminMemberService {

private final MemberRepository memberRepository;
private final ExcelUtil excelUtil;
private final AdminRecruitmentService adminRecruitmentService;
private final RecruitmentRoundRepository recruitmentRoundRepository;
private final MemberValidator memberValidator;
private final MemberUtil memberUtil;
private final EnvironmentUtil environmentUtil;
private final MembershipService membershipService;

public Page<AdminMemberResponse> searchMembers(MemberQueryOption queryOption, Pageable pageable) {
Page<Member> members = memberRepository.searchMembers(queryOption, pageable);
Expand Down Expand Up @@ -63,12 +72,36 @@ public byte[] createExcel() throws IOException {

@Transactional
public void demoteAllRegularMembersToAssociate(MemberDemoteRequest request) {
adminRecruitmentService.validateRecruitmentNotStarted(request.academicYear(), request.semesterType());
List<RecruitmentRound> recruitmentRounds = recruitmentRoundRepository.findAllByAcademicYearAndSemesterType(
request.academicYear(), request.semesterType());

memberValidator.validateMemberDemote(recruitmentRounds);

List<Member> regularMembers = memberRepository.findAllByRole(MemberRole.REGULAR);

regularMembers.forEach(Member::demoteToAssociate);
log.info(
"[AdminMemberService] 정회원 일괄 강등: demotedMemberIds={}",
regularMembers.stream().map(Member::getId).toList());
}

/**
* 정회원 조건 PENDING으로 변경, 준회원 조건 PENDING으로 변경
*/
@Transactional
public void demoteToGuestAndRegularRequirementToPending() {
validateProfile();
Member member = memberUtil.getCurrentMember();
member.demoteToGuest();

membershipService.deleteMembership(member);

log.info("[AdminMemberService] 게스트로 강등: demotedMemberId={}", member.getId());
}

private void validateProfile() {
if (!environmentUtil.isDevAndLocalProfile()) {
throw new CustomException(FORBIDDEN);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import org.springframework.data.support.PageableExecutionUtils;

@RequiredArgsConstructor
public class MemberCustomRepositoryImpl extends MemberQueryMethod implements MemberCustomRepository {
public class MemberCustomRepositoryImpl implements MemberCustomRepository, MemberQueryMethod {

private final JPAQueryFactory queryFactory;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,50 +11,50 @@
import com.querydsl.core.types.dsl.EnumPath;
import java.util.List;

public class MemberQueryMethod {
public interface MemberQueryMethod {

protected BooleanExpression eqRole(MemberRole role) {
default BooleanExpression eqRole(MemberRole role) {
return role != null ? member.role.eq(role) : null;
}

protected BooleanExpression eqRoles(List<MemberRole> roles) {
default BooleanExpression eqRoles(List<MemberRole> roles) {
return roles != null && !roles.isEmpty() ? member.role.in(roles) : null;
}

protected BooleanExpression eqStudentId(String studentId) {
default BooleanExpression eqStudentId(String studentId) {
return studentId != null ? member.studentId.containsIgnoreCase(studentId) : null;
}

protected BooleanExpression eqName(String name) {
default BooleanExpression eqName(String name) {
return name != null ? member.name.containsIgnoreCase(name) : null;
}

protected BooleanExpression eqPhone(String phone) {
default BooleanExpression eqPhone(String phone) {
return phone != null ? member.phone.contains(phone.replaceAll("-", "")) : null;
}

protected BooleanExpression eqEmail(String email) {
default BooleanExpression eqEmail(String email) {
return email != null ? member.email.containsIgnoreCase(email) : null;
}

protected BooleanExpression eqDiscordUsername(String discordUsername) {
default BooleanExpression eqDiscordUsername(String discordUsername) {
return discordUsername != null ? member.discordUsername.containsIgnoreCase(discordUsername) : null;
}

protected BooleanExpression eqNickname(String nickname) {
default BooleanExpression eqNickname(String nickname) {
return nickname != null ? member.nickname.containsIgnoreCase(nickname) : null;
}

protected BooleanExpression eqRequirementStatus(
default BooleanExpression eqRequirementStatus(
EnumPath<RequirementStatus> requirement, RequirementStatus requirementStatus) {
return requirementStatus != null ? requirement.eq(requirementStatus) : null;
}

protected BooleanExpression inDepartmentList(List<Department> departmentCodes) {
default BooleanExpression inDepartmentList(List<Department> departmentCodes) {
return departmentCodes.isEmpty() ? null : member.department.in(departmentCodes);
}

protected BooleanBuilder matchesQueryOption(MemberQueryOption queryOption) {
default BooleanBuilder matchesQueryOption(MemberQueryOption queryOption) {
return new BooleanBuilder()
.and(eqStudentId(queryOption.studentId()))
.and(eqName(queryOption.name()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,14 @@ public void checkVerifiableUniv() {
throw new CustomException(EMAIL_ALREADY_SATISFIED);
}
}

/**
* 모든 준회원 조건을 강등합니다.
*/
public void demoteAssociateRequirement() {
bevyStatus = PENDING;
discordStatus = PENDING;
infoStatus = PENDING;
univStatus = PENDING;
}
}
22 changes: 22 additions & 0 deletions src/main/java/com/gdschongik/gdsc/domain/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ public void advanceToRegular() {

role = REGULAR;
}

/**
* 정회원에서 준회원으로 강등합니다.
*/
Expand All @@ -244,6 +245,27 @@ public void demoteToAssociate() {
role = ASSOCIATE;
}

/**
* 테스트 환경 구성을 위한 사용자 상태 변경 메소드
* 1. 멤버 역할을 GUEST로 강등
* 2. 준회원 가입 조건을 'PENDING'으로 변경
*/
public void demoteToGuest() {
role = GUEST;

univEmail = null;
name = null;
department = null;
studentId = null;
phone = null;

discordId = null;
nickname = null;
discordUsername = null;

associateRequirement.demoteAssociateRequirement();
}

// 기타 상태 변경 로직

public void updateLastLoginAt() {
Expand Down
Loading

0 comments on commit c699d39

Please sign in to comment.