Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] 회원 탈퇴 로직 분리 및 중복 확인 api 수정 #40

Merged
merged 2 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.follow_me.running_mate.config.security;

import org.springframework.context.annotation.Configuration;

import java.util.Arrays;
import java.util.stream.Stream;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SecurityConstant {
Expand All @@ -13,7 +14,8 @@ public class SecurityConstant {
"/api/auth/refresh",
"/api/images",
"/api/images/delete",
"/api/members/check/**"
"/api/members/check/**",
"/api/crew/check/**",
};

// Swagger UI 관련 공개 경로
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
import com.follow_me.running_mate.domain.member.entity.Member;
import com.follow_me.running_mate.global.error.exception.CustomException;
import io.lettuce.core.dynamic.annotation.Param;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

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

public interface CrewRepository extends JpaRepository<Crew,Long> {
@Query("SELECT c FROM Crew c WHERE c NOT IN :myCrews ORDER BY c.createdAt DESC")
List<Crew> findTop4ByIdNotInOrderByCreatedAtDesc(@Param("myCrews") List<Crew> myCrews);
Expand All @@ -18,7 +19,8 @@ default Crew getCrew(Long crewId) {
return findById(crewId)
.orElseThrow(() -> new CustomException(CrewErrorCode.NOT_FOUND)); // 크루가 없을 경우 예외 처리
}
boolean existsByName(String name);
@Query(value = "SELECT EXISTS(SELECT 1 FROM crew WHERE name = :name)", nativeQuery = true)
boolean existsByName(@Param("name") String name);
Optional<Crew> findByLeader(Member leader);
@Query(value = "SELECT DISTINCT c.* FROM crew c " +
"LEFT JOIN crew_location l ON c.id = l.crew_id " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
import com.follow_me.running_mate.domain.enums.CrewMemberStatus;
import com.follow_me.running_mate.domain.enums.Ranking;
import com.follow_me.running_mate.domain.member.entity.Member;
import org.springframework.web.multipart.MultipartFile;

import java.time.YearMonth;
import java.util.List;
import org.springframework.web.multipart.MultipartFile;

public interface CrewService {
List<Crew> getCrewByCourse(Course course);
Expand Down Expand Up @@ -49,4 +50,6 @@ CrewResponse.recommendedCrewListResponse searchCrews(
void leaveCrew(Member member, Long crewId);
CrewResponse.DuplicateCheckResponse isNameDuplicate(String nickname);
CrewResponse.CrewUpdateResponse getUpdateCrewInfo(Member member, Long crewId);

void softDeleteCrew(Member member);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,11 @@
import com.follow_me.running_mate.domain.course.service.review.CourseReviewService;
import com.follow_me.running_mate.domain.crew.dto.request.CrewRequest;
import com.follow_me.running_mate.domain.crew.dto.response.CrewResponse;
import com.follow_me.running_mate.domain.crew.entity.Crew;
import com.follow_me.running_mate.domain.crew.entity.CrewActivityTime;
import com.follow_me.running_mate.domain.crew.entity.CrewCourse;
import com.follow_me.running_mate.domain.crew.entity.CrewImage;
import com.follow_me.running_mate.domain.crew.entity.CrewLocation;
import com.follow_me.running_mate.domain.crew.entity.CrewMember;
import com.follow_me.running_mate.domain.crew.entity.CrewSchedule;
import com.follow_me.running_mate.domain.crew.entity.CrewScheduleApply;
import com.follow_me.running_mate.domain.crew.entity.*;
import com.follow_me.running_mate.domain.crew.exception.CrewErrorCode;
import com.follow_me.running_mate.domain.crew.mapper.CrewEntityMapper;
import com.follow_me.running_mate.domain.crew.mapper.CrewResponseMapper;
import com.follow_me.running_mate.domain.crew.repository.CrewActivityTimeRepository;
import com.follow_me.running_mate.domain.crew.repository.CrewCourseRepository;
import com.follow_me.running_mate.domain.crew.repository.CrewImageRepository;
import com.follow_me.running_mate.domain.crew.repository.CrewLocationRepository;
import com.follow_me.running_mate.domain.crew.repository.CrewMemberRepository;
import com.follow_me.running_mate.domain.crew.repository.CrewRepository;
import com.follow_me.running_mate.domain.crew.repository.CrewScheduleApplyRepository;
import com.follow_me.running_mate.domain.crew.repository.CrewScheduleRepository;
import com.follow_me.running_mate.domain.crew.repository.*;
import com.follow_me.running_mate.domain.enums.ActivityTimeType;
import com.follow_me.running_mate.domain.enums.CrewMemberStatus;
import com.follow_me.running_mate.domain.enums.CrewScheduleApplyStatus;
Expand All @@ -37,6 +23,11 @@
import com.follow_me.running_mate.domain.member.service.MemberService;
import com.follow_me.running_mate.global.common.service.S3ImageService;
import com.follow_me.running_mate.global.error.exception.CustomException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
Expand All @@ -45,10 +36,6 @@
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -109,7 +96,7 @@ public CrewResponse.recommendedCrewListResponse searchCrews(
: activityTimes.stream()
.map(ActivityTimeType::name)
.toList();
searchCrews = crewRepository.searchCrews(keyword, city, district, activityTimeList,myCrewIds);
searchCrews = crewRepository.searchCrews(keyword, city, district, activityTimeList, myCrewIds);
searchCrews = searchCrews.stream()
.filter(crew -> {
if (ranking == null) {
Expand All @@ -118,7 +105,7 @@ public CrewResponse.recommendedCrewListResponse searchCrews(
return crew.getRankingValue(crew.getRanking()) >= crew.getRankingValue(ranking);
})
.collect(Collectors.toList());
if(searchCrews.isEmpty()){
if (searchCrews.isEmpty()) {
return new CrewResponse.recommendedCrewListResponse(List.of());
}
}
Expand All @@ -128,8 +115,8 @@ public CrewResponse.recommendedCrewListResponse searchCrews(
searchCrews.sort(Comparator.comparing(Crew::getCreatedAt)); // 오래된 순
}
List<Long> searchSumFootprint = searchCrews.stream()
.map(crews -> crewMemberRepository.sumFootprintByCrewAndStatus(crews, CrewMemberStatus.COMPLETE))
.toList();
.map(crews -> crewMemberRepository.sumFootprintByCrewAndStatus(crews, CrewMemberStatus.COMPLETE))
.toList();

return new CrewResponse.recommendedCrewListResponse(crewResponseMapper.toCrewInfoResponse(searchCrews, searchSumFootprint));
}
Expand Down Expand Up @@ -222,7 +209,7 @@ public List<CrewResponse.CrewMemberInfo> getMembersBySchedule(Long scheduleId) {
@Override
@Transactional
public CrewResponse.CrewIdResponse createCrew(
Member leader, CrewRequest.createCrew request, MultipartFile representativeImage
Member leader, CrewRequest.createCrew request, MultipartFile representativeImage
) {
String imageUrl = (representativeImage != null) ? s3ImageService.upload(representativeImage) : null;
Crew crew = crewEntityMapper.toCrew(leader, request, imageUrl);
Expand Down Expand Up @@ -481,7 +468,7 @@ public CrewResponse.CrewApplyResponse cancelCrewApplication(Member member, Long
public void deleteCrew(Member member, Long crewId) {
Crew crew = crewRepository.getCrew(crewId);
List<CrewMember> crewMembers = crewMemberRepository.findAllByCrew(crew);
boolean isOnlyLeader = crewMembers.size() == 1 && isUserLeaderOfCrew(crewMembers.get(0).getMember(),crew);
boolean isOnlyLeader = crewMembers.size() == 1 && isUserLeaderOfCrew(crewMembers.get(0).getMember(), crew);

// 크루에 다른 멤버가 있다면 리더 검증
if (!isOnlyLeader) {
Expand Down Expand Up @@ -532,6 +519,7 @@ public void leaveCrew(Member member, Long crewId) {
.orElseThrow(() -> new IllegalArgumentException("해당 크루에 가입되어 있지 않습니다."));
crewMemberRepository.delete(crewMember);
}

@Override
@Transactional
public CrewResponse.DuplicateCheckResponse isNameDuplicate(String name) {
Expand All @@ -546,9 +534,9 @@ public CrewResponse.CrewUpdateResponse getUpdateCrewInfo(Member member, Long cre
validateCrewLeader(member, crew);

return crewResponseMapper.toCrewUpdateResponse(
crew,
getCrewActivityTime(crew),
getCrewLocationInfo(crew)
crew,
getCrewActivityTime(crew),
getCrewLocationInfo(crew)
);
}

Expand Down Expand Up @@ -626,4 +614,31 @@ private void updateApplyStatus(CrewScheduleApply apply, List<Long> memberIds) {
: CrewScheduleApplyStatus.ABSENCE;
apply.setStatus(status);
}
@Override
@Transactional
public void softDeleteCrew(Member member) {
if (crewRepository.existsByLeader(member)) {
crewRepository.findByLeader(member).ifPresent(this::softDeleteCrew);
}
}

private void softDeleteCrew(Crew crew) {
List<CrewMember> crewMembers = crewMemberRepository.findAllByCrew(crew);

crewMembers.forEach(CrewMember::delete);

crewActivityTimeRepository.findAllByCrew(crew).forEach(CrewActivityTime::delete);
crewCourseRepository.findAllByCrew(crew).forEach(CrewCourse::delete);
crewImageRepository.findAllByCrew(crew).forEach(CrewImage::delete);
crewLocationRepository.findAllByCrew(crew).forEach(CrewLocation::delete);
crewMemberRepository.findAllByCrew(crew).forEach(CrewMember::delete);

List<CrewSchedule> schedules = crewScheduleRepository.findAllByCrew(crew);
for (CrewSchedule schedule : schedules) {
crewScheduleApplyRepository.findAllByCrewSchedule(schedule).forEach(CrewScheduleApply::delete);
schedule.delete();
}
crew.delete();
crewRepository.save(crew);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package com.follow_me.running_mate.domain.member.controller;

import static com.follow_me.running_mate.config.security.jwt.JwtConstant.REFRESH_TOKEN_HEADER;

import com.follow_me.running_mate.config.security.auth.PrincipalDetails;
import com.follow_me.running_mate.domain.member.dto.request.MemberRequest;
import com.follow_me.running_mate.domain.member.service.MemberService;
import com.follow_me.running_mate.domain.member.service.auth.AuthService;
import com.follow_me.running_mate.global.common.BaseResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -17,22 +15,18 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import static com.follow_me.running_mate.config.security.jwt.JwtConstant.REFRESH_TOKEN_HEADER;

@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@Tag(name = "Auth", description = "회원 인증 API")
public class AuthController {

private final MemberService memberService;
private final AuthService authService;

@PostMapping("/login")
@Operation(summary = "로그인 API", description = "이메일과 비밀번호로 로그인합니다.")
Expand Down Expand Up @@ -77,7 +71,7 @@ public BaseResponse<Void> signup(
@RequestPart(name = "request") @Valid MemberRequest.SignUpRequest request,
@RequestPart(name = "profileImage", required = false) MultipartFile profileImage
) {
memberService.signup(request, profileImage);
authService.signup(request, profileImage);
return BaseResponse.success("회원 가입에 성공했습니다.", null);
}

Expand All @@ -88,7 +82,7 @@ public BaseResponse<Void> signup(
content = @Content(mediaType = "application/json", schema = @Schema(implementation = BaseResponse.class))),
})
public BaseResponse<Void> logout(@AuthenticationPrincipal PrincipalDetails principalDetails) {
memberService.logout(principalDetails.getUsername());
authService.logout(principalDetails.getUsername());
return BaseResponse.success("로그아웃에 성공했습니다.", null);
}

Expand All @@ -102,7 +96,7 @@ public BaseResponse<Void> withdraw(
@AuthenticationPrincipal PrincipalDetails principalDetails,
@RequestBody MemberRequest.WithdrawRequest request
) {
memberService.withdraw(principalDetails.member(), request);
authService.withdraw(principalDetails.member(), request);
return BaseResponse.success("회원 탈퇴에 성공했습니다.", null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import com.follow_me.running_mate.domain.member.entity.Member;
import com.follow_me.running_mate.domain.member.exception.MemberErrorCode;
import com.follow_me.running_mate.global.error.exception.CustomException;
import java.util.Optional;
import io.lettuce.core.dynamic.annotation.Param;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {

Expand All @@ -16,6 +19,8 @@ default Member getMember(Long id) {

Optional<Member> findByEmail(String email);
Optional<Member> findByNickname(String nickname);
boolean existsByNickname(String nickname);
boolean existsByEmail(String email);
@Query(value = "SELECT EXISTS(SELECT 1 FROM member WHERE nickname = :nickname)", nativeQuery = true)
boolean existsByNickname(@Param("nickname") String nickname);
@Query(value = "SELECT EXISTS(SELECT 1 FROM member WHERE email = :email)", nativeQuery = true)
boolean existsByEmail(@Param("email") String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
import com.follow_me.running_mate.domain.member.dto.request.MemberRequest;
import com.follow_me.running_mate.domain.member.dto.response.MemberResponse;
import com.follow_me.running_mate.domain.member.entity.Member;
import java.time.YearMonth;
import org.springframework.web.multipart.MultipartFile;

import java.time.YearMonth;

public interface MemberService {

void signup(MemberRequest.SignUpRequest request, MultipartFile profileImage);
void logout(String email);
void withdraw(Member member, MemberRequest.WithdrawRequest request);
MemberResponse.MyProfileResponse getMyProfile(Member member);
MemberResponse.MyProfileSummaryResponse getMyProfileSummary(Member member);
MemberResponse.UpdateMyProfileResponse updateProfile(
Expand Down
Loading
Loading