Skip to content

Commit

Permalink
Merge branch 'dev' into refactor(#590)-ranking
Browse files Browse the repository at this point in the history
  • Loading branch information
j-ra1n authored Sep 5, 2024
2 parents a3205ff + fe538ca commit c4e177b
Show file tree
Hide file tree
Showing 29 changed files with 279 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public ResponseEntity<AuthLoginResponse> register(@AuthenticationPrincipal User
public ResponseEntity<Void> userDelete(@AuthenticationPrincipal User user,
@Valid @RequestBody MessageRequest request) {

authService.userDelete(user.getUsername(), request);
authService.userDelete(user, request.getMessage());

return ResponseEntity.ok().build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
import com.example.backend.domain.define.fcm.repository.FcmTokenRepository;
import com.example.backend.domain.define.refreshToken.RefreshToken;
import com.example.backend.domain.define.refreshToken.repository.RefreshTokenRepository;
import com.example.backend.study.api.controller.member.request.MessageRequest;
import com.example.backend.study.api.service.github.GithubApiTokenService;
import com.example.backend.study.api.service.info.StudyManagementService;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -57,6 +57,7 @@ public class AuthService {
private final FcmTokenRepository fcmTokenRepository;
private final GithubApiTokenService githubApiTokenService;
private final ApplicationEventPublisher eventPublisher;
private final StudyManagementService studyManagementService;

@Transactional
public AuthLoginResponse login(UserPlatformType platformType, String code, String state) {
Expand Down Expand Up @@ -88,9 +89,7 @@ public AuthLoginResponse login(UserPlatformType platformType, String code, Strin
});

// 깃허브 api 토큰 저장
log.info("로그인 후 받은 토큰을 저장하는 중.. (userId: {})", findUser.getId());
githubApiTokenService.saveToken(loginResponse.getGithubApiToken(), findUser.getId());
log.info("로그인 후 받은 토큰 저장 완료 (userId: {})", findUser.getId());

// JWT 토큰 생성
JwtToken jwtToken = generateJwtToken(findUser);
Expand Down Expand Up @@ -211,30 +210,28 @@ public AuthLoginResponse register(AuthServiceRegisterRequest request, User user)
}

@Transactional
public void userDelete(String userName, MessageRequest request) {
String[] platformIdAndPlatformType = extractFromSubject(userName);
String platformId = platformIdAndPlatformType[0];
String platformType = platformIdAndPlatformType[1];
User user = userRepository.findByPlatformIdAndPlatformType(platformId, UserPlatformType.valueOf(platformType)).orElseThrow(() -> {
log.warn(">>>> User Delete Fail : {}", ExceptionMessage.AUTH_NOT_FOUND.getText());
return new AuthException(ExceptionMessage.AUTH_NOT_FOUND);
});
public void userDelete(User contextUser, String reason) {
User findUser = userRepository.findByPlatformIdAndPlatformType(contextUser.getPlatformId(), contextUser.getPlatformType())
.orElseThrow(() -> {
log.error(">>>> User not found for platformId {} and platformType {} <<<<", contextUser.getPlatformId(), contextUser.getPlatformType());
throw new UserException(ExceptionMessage.USER_NOT_FOUND);
});

try {
user.reason(request.getMessage());
user.deleteUser();
// 랭킹 스코어 삭제
rankingService.deleteUserScore(user.getId());
log.info(">>>> {} Info is Deleted.", user.getName());
} catch (IllegalArgumentException e) {
log.error(">>>> ID = {} : 계정 삭제에 실패했습니다.", user.getId());
throw new AuthException(ExceptionMessage.AUTH_DELETE_FAIL);
}
}
// 랭킹 스코어 삭제
rankingService.deleteUserScore(findUser.getId());

// 사용자가 운영하던 스터디 종료
studyManagementService.closeStudiesOwnedByUser(findUser.getId());

// 사용자가 참여하던 스터디에서 활동 종료
studyManagementService.inactiveUserFromAllStudies(findUser.getId());

// 깃허브 토큰 삭제
githubApiTokenService.deleteToken(findUser.getId());

private String[] extractFromSubject(String subject) {
// "_"로 문자열을 나누고 id와 type을 추출
return subject.split("_");
// 사용자 탈퇴 및 개인 정보 삭제
findUser.withdrawal(reason);
log.info(">>>> 회원 탈퇴가 완료되었습니다. user id: {}", findUser.getId());
}

public UserInfoResponse authenticate(Long userId, User user) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,23 @@ public void updatePushAlarmYn(boolean pushAlarmEnable) {
this.pushAlarmYn = pushAlarmEnable;
}

public void deleteUser() {
public void withdrawal(String reason) {
this.role = UserRole.WITHDRAW;
this.name = "탈퇴한 사용자";
this.platformId = "DELETED";
this.socialInfo = null;
this.githubId = null;
this.profileImageUrl = null;
this.score = 0;
this.point = 0;
this.withdrawalReason = reason;
}

// Score 업데이트 메서드
public void addUserScore(int score) {
this.score = Math.max(0, this.score + score);
}

// 탈퇴 이유 메서드
public void reason(String withdrawalReason) {
this.withdrawalReason = withdrawalReason;
}

// Spring Security UserDetails Area
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
import com.example.backend.domain.define.study.info.StudyInfo;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface StudyInfoRepository extends JpaRepository<StudyInfo, Long>, StudyInfoRepositoryCustom {
List<StudyInfo> findAllByUserId(Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ public interface StudyInfoRepositoryCustom {

// 레포지토리 정보를 통해 스터디 조회
Optional<StudyInfo> findByRepositoryFullName(String owner, String repositoryName);

// 해당 아이디가 스터디장인 스터디 전부 활동 종료
void closeStudiesOwnedByUserId(Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -137,4 +138,20 @@ public Optional<StudyInfo> findByRepositoryFullName(String owner, String reposit
.fetchOne());
}

@Override
@Transactional
public void closeStudiesOwnedByUserId(Long userId) {
List<Long> studyIdList = queryFactory.select(studyInfo.id)
.from(studyInfo)
.where(studyInfo.userId.eq(userId))
.fetch();

if (studyIdList.isEmpty()) return;

queryFactory.update(studyInfo)
.set(studyInfo.status, StudyStatus.STUDY_INACTIVE)
.where(studyInfo.id.in(studyIdList))
.execute();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@ public interface StudyMemberRepositoryCustom {

// GitHubId와 StudyInfoId를 통해 사용자가 해당 스터디의 활동중인 멤버인지 판별한다.
boolean existsStudyMemberByGithubIdAndStudyInfoId(String githubId, Long studyInfoId);

// userId에 해당하는 스터디 멤버 상태를 전부 비활성화 시킨다.
void inActiveFromAllStudiesByUserId(Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

Expand Down Expand Up @@ -169,4 +170,15 @@ public boolean existsStudyMemberByGithubIdAndStudyInfoId(String githubId, Long s
.and(studyMember.status.eq(StudyMemberStatus.STUDY_ACTIVE)))
.fetchFirst() != null;
}

@Override
@Transactional
public void inActiveFromAllStudiesByUserId(Long userId) {
queryFactory.update(studyMember)
.set(studyMember.status, StudyMemberStatus.STUDY_WITHDRAWAL)
.where(studyMember.userId.eq(userId)
.and(studyMember.status.ne(StudyMemberStatus.STUDY_WITHDRAWAL)))
.execute();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,7 @@ public StudyInfoRegisterResponse registerStudy(StudyInfoRegisterRequest request,
registerDefaultConvention(studyInfo.getId());

// github에 스터디 레포지토리 생성
log.info("스터디 레포지토리를 생성하기 위해 토큰 조회 중.. (userId: {})", userInfo.getUserId());
GithubApiToken token = githubApiTokenService.getToken(userInfo.getUserId());
log.info("스터디 레포지토리를 생성하기 위한 토큰 조회 완료 (userId: {})", userInfo.getUserId());

githubApiService.createRepository(token.githubApiToken(), studyInfo.getRepositoryInfo(), "README.md를 작성해주세요.");

Expand Down Expand Up @@ -327,9 +325,7 @@ public static List<StudyInfoListWithMemberResponse> convertToWithMemberResponse(
public void checkDuplicateRepoName(UserInfoResponse userInfo, String repoName) {

// 사용자의 깃허브 토큰 조회
log.info("레포지토리 이름 중복 체크 전 토큰 조회 중.. (userId: {})", userInfo.getUserId());
GithubApiToken token = githubApiTokenService.getToken(userInfo.getUserId());
log.info("레포지토리 이름 중복 체크 전 토큰 조회 완료 (userId: {})", userInfo.getUserId());

// 레포지토리 이름 중복 확인
if (githubApiService.repositoryExists(token.githubApiToken(), userInfo.getGithubId(), repoName)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.backend.study.api.service.info;

import com.example.backend.domain.define.study.info.repository.StudyInfoRepository;
import com.example.backend.domain.define.study.member.repository.StudyMemberRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class StudyManagementService {
private final StudyInfoRepository studyInfoRepository;
private final StudyMemberRepository studyMemberRepository;
@Transactional
public void closeStudiesOwnedByUser(Long userId) {
studyInfoRepository.closeStudiesOwnedByUserId(userId);
log.info(">>>> Closed studies owned by {} <<<<", userId);
}

@Transactional
public void inactiveUserFromAllStudies(Long userId) {
studyMemberRepository.inActiveFromAllStudiesByUserId(userId);
log.info(">>>> Inactivated user {} from all studies <<<<", userId);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.example.backend.study.api.service.todo;


import static com.example.backend.domain.define.study.todo.mapping.constant.StudyTodoStatus.TODO_COMPLETE;

import com.example.backend.common.exception.ExceptionMessage;
import com.example.backend.common.exception.study.StudyInfoException;
import com.example.backend.common.exception.todo.TodoException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

Expand Down Expand Up @@ -164,7 +163,7 @@ void validUserTokenRequestWithDrawThenUserDelete() throws Exception {
.message("reason")
.build();

doNothing().when(authService).userDelete(any(String.class), any(MessageRequest.class));
doNothing().when(authService).userDelete(any(User.class), any(String.class));

// when
mockMvc.perform(post("/auth/delete")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@
import com.example.backend.domain.define.refreshToken.repository.RefreshTokenRepository;
import com.example.backend.domain.define.study.github.GithubApiToken;
import com.example.backend.domain.define.study.github.repository.GithubApiTokenRepository;
import com.example.backend.study.api.controller.member.request.MessageRequest;
import com.example.backend.domain.define.study.info.StudyInfo;
import com.example.backend.domain.define.study.info.StudyInfoFixture;
import com.example.backend.domain.define.study.info.constant.StudyStatus;
import com.example.backend.domain.define.study.info.repository.StudyInfoRepository;
import com.example.backend.domain.define.study.member.StudyMember;
import com.example.backend.domain.define.study.member.StudyMemberFixture;
import com.example.backend.domain.define.study.member.constant.StudyMemberStatus;
import com.example.backend.domain.define.study.member.repository.StudyMemberRepository;
import io.jsonwebtoken.Claims;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
Expand All @@ -32,6 +39,7 @@
import org.springframework.boot.test.mock.mockito.MockBean;

import java.util.HashMap;
import java.util.List;
import java.util.Optional;

import static com.example.backend.auth.config.fixture.UserFixture.*;
Expand Down Expand Up @@ -66,11 +74,19 @@ class AuthServiceTest extends MockTestConfig {
@Autowired
private GithubApiTokenRepository githubApiTokenRepository;

@Autowired
private StudyInfoRepository studyInfoRepository;

@Autowired
private StudyMemberRepository studyMemberRepository;

@AfterEach
void tearDown() {
githubApiTokenRepository.deleteAll();
userRepository.deleteAllInBatch();
githubApiTokenRepository.deleteAll();
studyInfoRepository.deleteAllInBatch();
studyMemberRepository.deleteAllInBatch();
}

@Test
Expand Down Expand Up @@ -221,32 +237,22 @@ public void registerUnauthUserFailTest() {
@DisplayName("존재하지 않는 userName으로 계정삭제를 진행할 수 없다.")
void isNotProcessingWhenUserNameIsNotExist() {
// given
String invalidUserName = "1234_KAKAO";
User notSavedUser = generateAuthUser();

// when
assertThrows(AuthException.class,
() -> authService.userDelete(invalidUserName, MessageRequest.builder()
.message("reason").build()));
UserException e = assertThrows(UserException.class, () -> authService.userDelete(notSavedUser, "reason"));
assertEquals(ExceptionMessage.USER_NOT_FOUND.getText(), e.getMessage());
}

@Test
@DisplayName("존재하는 계정의 userName으로 계정삭제를 진행할 수 있다.")
void successProcessingWhenUserNameIsExistInDB() {
String platformId = "1234";
String platformType = "KAKAO";
// given
User user = User.builder()
.platformId(platformId)
.platformType(UserPlatformType.valueOf(platformType))
.name("김민수")
.profileImageUrl("google.co.kr")
.role(USER)
.build();
userRepository.save(user);
User user = userRepository.save(generateAuthUser());

// when
authService.userDelete(platformId + "_" + platformType, MessageRequest.builder().message("reason").build());
User deletedUser = userRepository.findByPlatformIdAndPlatformType(user.getPlatformId(), user.getPlatformType()).orElse(null);
authService.userDelete(user, "reason");
User deletedUser = userRepository.findById(user.getId()).get();

// then
assertThat(deletedUser.getRole()).isEqualTo(UserRole.WITHDRAW);
Expand Down Expand Up @@ -429,4 +435,30 @@ void getUserByInfoTest() {
assertFalse(deletedRefreshToken.isPresent());
}

@Test
void 회원_탈퇴_최종_테스트() {
// given
User userA = userRepository.save(UserFixture.generateAuthUser());
User userB = userRepository.save(UserFixture.generateAuthJusung());

// 탈퇴 회원의 스터디
StudyInfo studyA = studyInfoRepository.save(StudyInfoFixture.generateStudyInfo(userA.getId()));
studyMemberRepository.save(StudyMemberFixture.createDefaultStudyMember(userA.getId(), studyA.getId()));

// 탈퇴 회원이 참여중인 스터디
StudyInfo studyB = studyInfoRepository.save(StudyInfoFixture.generateStudyInfo(userB.getId()));
studyMemberRepository.save(StudyMemberFixture.createDefaultStudyMember(userA.getId(), studyB.getId()));
studyMemberRepository.save(StudyMemberFixture.createDefaultStudyMember(userB.getId(), studyB.getId()));

// when
authService.userDelete(userA, "때리칠게요.");
List<StudyInfo> allByUserId = studyInfoRepository.findAllByUserId(userA.getId());
StudyMember member = studyMemberRepository.findByStudyInfoIdAndUserId(studyB.getId(), userA.getId()).get();

// then
assertSame(allByUserId.get(0).getStatus(), StudyStatus.STUDY_INACTIVE);
assertSame(StudyMemberStatus.STUDY_WITHDRAWAL, member.getStatus());

}

}
Loading

0 comments on commit c4e177b

Please sign in to comment.